public static bool TryGetMbrInfo(DiskImage hdd, out MbrPartitioningInfo info) { info = default; Debug.Assert(Unsafe.SizeOf <MbrHeader>() == MBR_SIZE); var headerBytes = hdd.ReadBytes(0, MBR_SIZE); MbrHeader header = MemoryMarshal.Cast <byte, MbrHeader>(new Span <byte>(headerBytes))[0]; if (header.BootSig != MbrHeader.MbrMagic) { return(false); } bool hasGpt = false; for (int i = 0; i < 4; i++) { var part = header.GetPartition(i); hasGpt |= part.Type == MbrPartitionType.GptProtective; } //MBR paritioned disks often contain boot loaders in non-paritioned space. //So to be safe copy all the data. List <FileExtent> partitions = new List <FileExtent>() { new FileExtent(MBR_SIZE, hdd.Length - MBR_SIZE), }; info = new MbrPartitioningInfo(headerBytes, partitions, hasGpt); return(true); }
private static GptHeader LoadHeader(DiskImage hdd, long offset) { Debug.Assert(offset % SectorSize == 0); var headerBytes = hdd.ReadBytes(offset, Unsafe.SizeOf <GptHeader>()); ref GptHeader ret = ref MemoryMarshal.Cast <byte, GptHeader>(new Span <byte>(headerBytes))[0];
public static bool TryGetGptInfo(DiskImage hdd, out GptPartitioningInfo info) { Debug.Assert(Unsafe.SizeOf <GptHeader>() == CurrentHeaderSize); Debug.Assert(Unsafe.SizeOf <PartitionEntry>() == PartitionEntrySize); info = null; MbrPartitioningInfo mbr; if (!MbrPartitioningInfo.TryGetMbrInfo(hdd, out mbr)) { return(false); } if (!mbr.HasGpt) { return(false); } var primaryHeader = LoadHeader(hdd, SectorSize); var backupHeader = LoadHeader(hdd, hdd.Length - SectorSize); //Make sure the headers agree on each other's locations if (primaryHeader.BackupLba != (hdd.Length / SectorSize) - 1) { throw new Exception("Location of backup LBA in primary header is wrong."); } if (backupHeader.BackupLba != 1) { throw new Exception("Location of primary LBA in backup header is wrong."); } //make sure the headers match if (primaryHeader.NumberOfPartitions != backupHeader.NumberOfPartitions) { throw new Exception("Primary and backup GPT headers do not agree on the number of partitions."); } if (primaryHeader.FirstUsableLba != backupHeader.FirstUsableLba || primaryHeader.LastUsableLba != backupHeader.LastUsableLba) { throw new Exception("Primary and backup GPT headers do not agree on the usable area of the disk."); } if (primaryHeader.DiskGuid != backupHeader.DiskGuid) { throw new Exception("Primary and backup GPT headers do not agree on the disk GUID."); } //TODO: Make sure the partition entries do not intersect with the usable area of the disk or the GptHeader. //at this point we have varified that the header for both the primary and backup //GPT are correct. We are only going to load paritions from the primary header. //Make sure the minimum space for partition entries was reserved if (primaryHeader.FirstUsableLba < (Program.RoundUp(MinimumSizeForParitionEntriesArea, SectorSize) / SectorSize + 2)) //extra two sectors for protective MBR and GPT header { throw new Exception("Not enough space for primary parition entries was reserved."); } if (primaryHeader.LastUsableLba >= Program.RoundDown(hdd.Length - MinimumSizeForParitionEntriesArea - SectorSize, SectorSize) / SectorSize) { throw new Exception("Not enough space for backup parition entries was reserved."); } byte[] partitionEntriesBytes = new byte[primaryHeader.NumberOfPartitions * PartitionEntrySize]; hdd.ReadBytes(new Span <byte>(partitionEntriesBytes), primaryHeader.StartingLbaOfPartitionEntries * SectorSize); uint actualEntriesCrc = Crc32Algorithm.Compute(partitionEntriesBytes); if (primaryHeader.CrcOfPartitionEntry != actualEntriesCrc) { throw new Exception("CRC of partition entries not correct."); } var partitions = new List <PartitionEntry>(); foreach (PartitionEntry part in MemoryMarshal.Cast <byte, PartitionEntry>(new ReadOnlySpan <byte>(partitionEntriesBytes))) { if (part.Type == Guid.Empty) { continue; } if (part.FirstLba < primaryHeader.FirstUsableLba || part.LastLba > primaryHeader.LastUsableLba) { throw new Exception($"Parition '{part.Name}' is outside the usable space."); } partitions.Add(part); } partitions.Sort((a, b) => a.FirstLba.CompareTo(b.FirstLba)); //TODO: make sure the paritions do not intersect with each other info = new GptPartitioningInfo(mbr, partitions); return(true); }
/// <summary> /// Copies the contents of the disk image to the disk. /// </summary> /// <param name="disk"></param> /// <param name="image"></param> /// <param name="progress">Recievies the percent complete progress.</param> /// <returns></returns> public static async Task WriteImageToDisk(Disk disk, DiskImage image, IProgress <int> progress) { if (image.Length > disk.Capacity) { throw new InvalidOperationException("Disk image is larger than the size of the selected disk."); } if (disk.Capacity % Program.SECTOR_SIZE != 0) { throw new InvalidOperationException("Disk capacity is not a multiple of sector size."); } IPartitioningInfo partInfo; bool imageLengthOk = image.Length % Program.SECTOR_SIZE == 0; bool hasPartitions = getParitionInfo(image, out partInfo); if (!imageLengthOk) { if (hasPartitions) { throw new InvalidOperationException($"Found partition info, but image file length is not a multiple of sector size ({SECTOR_SIZE} bytes). Maybe the file got partially truncated?"); } else { throw new PartitionInformationMissingException(); } } else if (!hasPartitions) { throw new PartitionInformationMissingException(); } var copyPlan = CreateCopyPlan(image, partInfo); await disk.Clean().ConfigureAwait(false); int access = Interop.Kernel32.GenericOperations.GENERIC_WRITE; using (var hDisk = Interop.Kernel32.CreateFile(disk.DiskPath, access, FileShare.ReadWrite, FileMode.Open, 0)) { if (hDisk.IsInvalid) { throw new Exception("Failed to open disk handle."); } using (var fs = new FileStream(hDisk, FileAccess.Write)) { var buffer = new byte[MAX_CHUNK]; for (int i = 0; i < copyPlan.Count; i++) { var chunk = copyPlan[i]; image.ReadBytes(new Span <byte>(buffer, 0, (int)chunk.Length), chunk.Offset); fs.Seek(chunk.Offset, SeekOrigin.Begin); await fs.WriteAsync(buffer, 0, (int)chunk.Length); progress.Report(i * 100 / copyPlan.Count); } if (partInfo != null) { await partInfo.WriteParitionTableAsync(fs, disk.Capacity); } await fs.FlushAsync(); fs.Flush(flushToDisk: true); } progress.Report(100); } disk.Eject(); }