Example #1
0
        private void ResumeMove(MoveExtentOperationResumeRecord resumeRecord)
        {
            DiskGroupLockResult result = DiskGroupHelper.LockDiskGroup(m_diskGroup);

            if (result == DiskGroupLockResult.CannotLockDisk)
            {
                MessageBox.Show("Unable to lock all disks!", "Error");
            }
            else if (result == DiskGroupLockResult.CannotLockVolume)
            {
                MessageBox.Show("Unable to lock all volumes!", "Error");
            }
            else if (result == DiskGroupLockResult.OneOrMoreDisksAreOfflineOrReadonly)
            {
                MessageBox.Show("One or more disks are offline or set to readonly.", "Error");
            }
            else if (result == DiskGroupLockResult.CannotTakeDiskOffline)
            {
                MessageBox.Show("Failed to take all dynamic disks offline!", "Error");
            }
            else if (result == DiskGroupLockResult.Success)
            {
                int extentIndex = DynamicDiskExtentHelper.GetIndexOfExtentID(m_volume.DynamicExtents, (resumeRecord).ExtentID);
                DynamicDiskExtent sourceExtent = m_volume.DynamicExtents[extentIndex];

                long   bytesTotal   = sourceExtent.Size;
                long   bytesCopied  = 0;
                Thread workerThread = new Thread(delegate()
                {
                    m_isWorking = true;
                    List <DynamicDisk> diskGroup = WindowsDynamicDiskHelper.GetPhysicalDynamicDisks(m_volume.DiskGroupGuid);
                    DiskGroupDatabase database   = DiskGroupDatabase.ReadFromDisks(diskGroup, m_volume.DiskGroupGuid);
                    MoveExtentHelper.ResumeMoveExtent(database, m_volume, resumeRecord, ref bytesCopied);
                    m_isWorking = false;
                });
                workerThread.Start();

                new Thread(delegate()
                {
                    while (workerThread.IsAlive)
                    {
                        Thread.Sleep(250);
                        int progress = (int)(100 * (double)bytesCopied / bytesTotal);
                        this.Invoke((MethodInvoker) delegate()
                        {
                            progressBar.Value = progress;
                        });
                    }

                    this.Invoke((MethodInvoker) delegate()
                    {
                        DiskGroupHelper.UnlockDiskGroup(m_diskGroup);
                        this.DialogResult = DialogResult.OK;
                        this.Close();
                    });
                }).Start();
            }
        }
 private static void ClearBackupData(Disk relocatedExtentDisk, MoveExtentOperationResumeRecord resumeRecord)
 {
     byte[] emptySector = new byte[relocatedExtentDisk.BytesPerSector];
     relocatedExtentDisk.WriteSectors((long)resumeRecord.BootRecordBackupSector, emptySector);
     if (resumeRecord.BackupBufferStartSector > 0)
     {
         byte[] emptyRegion = new byte[resumeRecord.BackupBufferSizeLBA * relocatedExtentDisk.BytesPerSector];
         relocatedExtentDisk.WriteSectors((long)resumeRecord.BackupBufferStartSector, emptyRegion);
     }
 }
        /// <summary>
        /// Move extent to a new location on the same disk
        /// </summary>
        public static void MoveExtentWithinSameDisk(DiskGroupDatabase database, DynamicVolume volume, DynamicDiskExtent sourceExtent, DiskExtent relocatedExtent, ref long bytesCopied)
        {
            MoveExtentOperationResumeRecord resumeRecord = new MoveExtentOperationResumeRecord();

            // 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 = PrivateRegionHelper.FindUnusedSector(dynamicDisk.PrivateHeader, dynamicDisk.TOCBlock);

            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 = (long)Math.Abs((double)resumeRecord.NewStartSector - resumeRecord.OldStartSector);

            if (distanceLBA < MoveHelper.BufferedModeThresholdLBA)
            {
                long backupBufferStartSector = PrivateRegionHelper.FindUnusedRegion(dynamicDisk.PrivateHeader, dynamicDisk.TOCBlock, 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(volume.BytesPerSector));

            if (sourceExtent.FirstSector < relocatedExtent.FirstSector)
            {
                // move right
                MoveExtentRight(database, 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(relocatedExtent.BytesPerSector));
                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(database.Disks, volume.VolumeGuid);
                MoveExtentLeft(database, volume, resumeRecord, ref bytesCopied);
            }
        }
        private static void MoveExtentLeft(DiskGroupDatabase database, DynamicVolume volume, MoveExtentOperationResumeRecord resumeRecord, ref long bytesCopied)
        {
            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(database.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 void MoveExtentRight(DiskGroupDatabase database, DynamicVolume volume, MoveExtentOperationResumeRecord resumeRecord, ref long bytesCopied)
        {
            DynamicDiskExtent sourceExtent    = GetVolumeExtent(volume, resumeRecord.ExtentID);
            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(database.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(DiskGroupDatabase database, DynamicVolume volume, MoveExtentOperationResumeRecord 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
                DynamicDiskExtent sourceExtent = GetVolumeExtent(volume, resumeRecord.ExtentID);
                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(database, volume, resumeRecord, ref bytesCopied);
            }
            else
            {
                MoveExtentLeft(database, volume, resumeRecord, ref bytesCopied);
            }
        }
        public static void MoveExtentDataLeft(Volume volume, DiskExtent sourceExtent, DiskExtent relocatedExtent, MoveExtentOperationResumeRecord resumeRecord, ref long bytesCopied)
        {
            // we make sure no data will be overwritten too soon:
            long distanceLBA  = (long)(resumeRecord.OldStartSector - resumeRecord.NewStartSector);
            bool bufferedMode = false;

            if (distanceLBA < BufferedModeThresholdLBA)
            {
                bufferedMode = true;
            }

            int transferSizeLBA;

            if (bufferedMode)
            {
                transferSizeLBA = (int)resumeRecord.BackupBufferSizeLBA;;
            }
            else
            {
                transferSizeLBA = (int)Math.Min(Settings.MaximumTransferSizeLBA, distanceLBA);
            }

            // move the data
            for (long sectorIndex = (long)resumeRecord.NumberOfCommittedSectors; sectorIndex < relocatedExtent.TotalSectors; sectorIndex += transferSizeLBA)
            {
                long sectorsLeft   = relocatedExtent.TotalSectors - sectorIndex;
                int  sectorsToRead = (int)Math.Min(transferSizeLBA, sectorsLeft);

                byte[] data = sourceExtent.ReadSectors(sectorIndex, sectorsToRead);
                if (bufferedMode)
                {
                    // we write the data to the buffer for recovery purposes
                    relocatedExtent.Disk.WriteSectors((long)resumeRecord.BackupBufferStartSector, data);
                    resumeRecord.RestoreFromBuffer = true;
                    relocatedExtent.WriteSectors(0, resumeRecord.GetBytes(relocatedExtent.BytesPerSector));
                }
                relocatedExtent.WriteSectors(sectorIndex, data);

                // update the resume record
                resumeRecord.RestoreFromBuffer         = false;
                resumeRecord.NumberOfCommittedSectors += (ulong)sectorsToRead;
                volume.WriteSectors(0, resumeRecord.GetBytes(volume.BytesPerSector));
                bytesCopied = (long)resumeRecord.NumberOfCommittedSectors * sourceExtent.BytesPerSector;
            }
        }
        public static void MoveExtentDataRight(Volume volume, DiskExtent sourceExtent, DiskExtent relocatedExtent, MoveExtentOperationResumeRecord resumeRecord, ref long bytesCopied)
        {
            // we make sure no data will be overwritten too soon:
            long distanceLBA  = (long)(resumeRecord.NewStartSector - resumeRecord.OldStartSector);
            bool bufferedMode = false;

            if (distanceLBA < BufferedModeThresholdLBA)
            {
                bufferedMode = true;
            }

            int transferSizeLBA;

            if (bufferedMode)
            {
                transferSizeLBA = (int)resumeRecord.BackupBufferSizeLBA;
            }
            else
            {
                transferSizeLBA = (int)Math.Min(Settings.MaximumTransferSizeLBA, distanceLBA);
            }

            // move the data
            for (long readCount = (long)resumeRecord.NumberOfCommittedSectors; readCount < relocatedExtent.TotalSectors; readCount += transferSizeLBA)
            {
                // we read (and write) from the end of the extent and progress to the left
                long sectorsLeft   = relocatedExtent.TotalSectors - readCount;
                int  sectorsToRead = (int)Math.Min(transferSizeLBA, sectorsLeft);

                long sectorIndex = relocatedExtent.TotalSectors - readCount - sectorsToRead;

                byte[] data = sourceExtent.ReadSectors(sectorIndex, sectorsToRead);

                if (bufferedMode)
                {
                    // we write the data to the buffer for recovery purposes
                    relocatedExtent.Disk.WriteSectors((long)resumeRecord.BackupBufferStartSector, data);
                    resumeRecord.RestoreFromBuffer = true;
                    // Note: if the extent we move is the first in the volume, we will write the resume record to
                    // the source extent, which is the one that the database is still referring to
                    volume.WriteSectors(0, resumeRecord.GetBytes(volume.BytesPerSector));
                }
                relocatedExtent.WriteSectors(sectorIndex, data);

                // update the resume record
                resumeRecord.RestoreFromBuffer         = false;
                resumeRecord.NumberOfCommittedSectors += (ulong)sectorsToRead;
                volume.WriteSectors(0, resumeRecord.GetBytes(volume.BytesPerSector));
                bytesCopied = (long)resumeRecord.NumberOfCommittedSectors * sourceExtent.BytesPerSector;
            }
        }