mirror of
https://github.com/ReneLergner/WPinternals.git
synced 2026-06-20 22:21:13 +10:00
Initial commit - WPinternals 2.6
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
internal class BiosExtendedPartitionTable
|
||||
{
|
||||
private Stream _disk;
|
||||
private uint _firstSector;
|
||||
|
||||
public BiosExtendedPartitionTable(Stream disk, uint firstSector)
|
||||
{
|
||||
_disk = disk;
|
||||
_firstSector = firstSector;
|
||||
}
|
||||
|
||||
public BiosPartitionRecord[] GetPartitions()
|
||||
{
|
||||
List<BiosPartitionRecord> result = new List<BiosPartitionRecord>();
|
||||
|
||||
uint partPos = _firstSector;
|
||||
while (partPos != 0)
|
||||
{
|
||||
_disk.Position = ((long)partPos) * Utilities.SectorSize;
|
||||
byte[] sector = Utilities.ReadFully(_disk, Utilities.SectorSize);
|
||||
if (sector[510] != 0x55 || sector[511] != 0xAA)
|
||||
{
|
||||
throw new IOException("Invalid extended partition sector");
|
||||
}
|
||||
|
||||
uint nextPartPos = 0;
|
||||
for (int offset = 0x1BE; offset <= 0x1EE; offset += 0x10)
|
||||
{
|
||||
BiosPartitionRecord thisPart = new BiosPartitionRecord(sector, offset, partPos, -1);
|
||||
|
||||
if (thisPart.StartCylinder != 0 || thisPart.StartHead != 0 || thisPart.StartSector != 0)
|
||||
{
|
||||
if (thisPart.PartitionType != 0x05 && thisPart.PartitionType != 0x0F)
|
||||
{
|
||||
result.Add(thisPart);
|
||||
}
|
||||
else
|
||||
{
|
||||
nextPartPos = _firstSector + thisPart.LBAStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partPos = nextPartPos;
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all of the disk ranges containing partition table data.
|
||||
/// </summary>
|
||||
/// <returns>Set of stream extents, indicated as byte offset from the start of the disk.</returns>
|
||||
public IEnumerable<StreamExtent> GetMetadataDiskExtents()
|
||||
{
|
||||
List<StreamExtent> extents = new List<StreamExtent>();
|
||||
|
||||
uint partPos = _firstSector;
|
||||
while (partPos != 0)
|
||||
{
|
||||
extents.Add(new StreamExtent(((long)partPos) * Utilities.SectorSize, Utilities.SectorSize));
|
||||
|
||||
_disk.Position = ((long)partPos) * Utilities.SectorSize;
|
||||
byte[] sector = Utilities.ReadFully(_disk, Utilities.SectorSize);
|
||||
if (sector[510] != 0x55 || sector[511] != 0xAA)
|
||||
{
|
||||
throw new IOException("Invalid extended partition sector");
|
||||
}
|
||||
|
||||
uint nextPartPos = 0;
|
||||
for (int offset = 0x1BE; offset <= 0x1EE; offset += 0x10)
|
||||
{
|
||||
BiosPartitionRecord thisPart = new BiosPartitionRecord(sector, offset, partPos, -1);
|
||||
|
||||
if (thisPart.StartCylinder != 0 || thisPart.StartHead != 0 || thisPart.StartSector != 0)
|
||||
{
|
||||
if (thisPart.PartitionType == 0x05 || thisPart.PartitionType == 0x0F)
|
||||
{
|
||||
nextPartPos = _firstSector + thisPart.LBAStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partPos = nextPartPos;
|
||||
}
|
||||
|
||||
return extents;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to partition records in a BIOS (MBR) partition table.
|
||||
/// </summary>
|
||||
public sealed class BiosPartitionInfo : PartitionInfo
|
||||
{
|
||||
private BiosPartitionRecord _record;
|
||||
private BiosPartitionTable _table;
|
||||
|
||||
internal BiosPartitionInfo(BiosPartitionTable table, BiosPartitionRecord record)
|
||||
{
|
||||
_table = table;
|
||||
_record = record;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first sector of the partion (relative to start of disk) as a Logical Block Address.
|
||||
/// </summary>
|
||||
public override long FirstSector
|
||||
{
|
||||
get { return _record.LBAStartAbsolute; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last sector of the partion (relative to start of disk) as a Logical Block Address (inclusive).
|
||||
/// </summary>
|
||||
public override long LastSector
|
||||
{
|
||||
get { return _record.LBAStartAbsolute + _record.LBALength - 1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Always returns <see cref="System.Guid"/>.Empty.
|
||||
/// </summary>
|
||||
public override Guid GuidType
|
||||
{
|
||||
get { return Guid.Empty; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the partition.
|
||||
/// </summary>
|
||||
public override byte BiosType
|
||||
{
|
||||
get { return _record.PartitionType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the partition as a string.
|
||||
/// </summary>
|
||||
public override string TypeAsString
|
||||
{
|
||||
get { return _record.FriendlyPartitionType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this partition is active (bootable).
|
||||
/// </summary>
|
||||
public bool IsActive
|
||||
{
|
||||
get { return _record.Status != 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start of the partition as a CHS address.
|
||||
/// </summary>
|
||||
public ChsAddress Start
|
||||
{
|
||||
get { return new ChsAddress(_record.StartCylinder, _record.StartHead, _record.StartSector); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end (inclusive) of the partition as a CHS address.
|
||||
/// </summary>
|
||||
public ChsAddress End
|
||||
{
|
||||
get { return new ChsAddress(_record.EndCylinder, _record.EndHead, _record.EndSector); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the partition in the primary partition table, or <c>-1</c> if not a primary partition.
|
||||
/// </summary>
|
||||
public int PrimaryIndex
|
||||
{
|
||||
get { return _record.Index; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the partition is a primary (rather than extended) partition.
|
||||
/// </summary>
|
||||
public bool IsPrimary
|
||||
{
|
||||
get { return PrimaryIndex >= 0; }
|
||||
}
|
||||
|
||||
internal override PhysicalVolumeType VolumeType
|
||||
{
|
||||
get { return PhysicalVolumeType.BiosPartition; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a stream to access the content of the partition.
|
||||
/// </summary>
|
||||
/// <returns>The new stream.</returns>
|
||||
public override SparseStream Open()
|
||||
{
|
||||
return _table.Open(_record);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
|
||||
internal class BiosPartitionRecord : IComparable<BiosPartitionRecord>
|
||||
{
|
||||
private uint _lbaOffset;
|
||||
|
||||
private byte _status;
|
||||
private ushort _startCylinder;
|
||||
private byte _startHead;
|
||||
private byte _startSector;
|
||||
private byte _type;
|
||||
private ushort _endCylinder;
|
||||
private byte _endHead;
|
||||
private byte _endSector;
|
||||
private uint _lbaStart;
|
||||
private uint _lbaLength;
|
||||
private int _index;
|
||||
|
||||
public BiosPartitionRecord()
|
||||
{
|
||||
}
|
||||
|
||||
public BiosPartitionRecord(byte[] data, int offset, uint lbaOffset, int index)
|
||||
{
|
||||
_lbaOffset = lbaOffset;
|
||||
|
||||
_status = data[offset];
|
||||
_startHead = data[offset + 1];
|
||||
_startSector = (byte)(data[offset + 2] & 0x3F);
|
||||
_startCylinder = (ushort)(data[offset + 3] | ((data[offset + 2] & 0xC0) << 2));
|
||||
_type = data[offset + 4];
|
||||
_endHead = data[offset + 5];
|
||||
_endSector = (byte)(data[offset + 6] & 0x3F);
|
||||
_endCylinder = (ushort)(data[offset + 7] | ((data[offset + 6] & 0xC0) << 2));
|
||||
_lbaStart = Utilities.ToUInt32LittleEndian(data, offset + 8);
|
||||
_lbaLength = Utilities.ToUInt32LittleEndian(data, offset + 12);
|
||||
_index = index;
|
||||
}
|
||||
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
return _endHead != 0 || _endSector != 0 || _endCylinder != 0 || _lbaLength != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public byte Status
|
||||
{
|
||||
get { return _status; }
|
||||
set { _status = value; }
|
||||
}
|
||||
|
||||
public ushort StartCylinder
|
||||
{
|
||||
get { return _startCylinder; }
|
||||
set { _startCylinder = value; }
|
||||
}
|
||||
|
||||
public byte StartHead
|
||||
{
|
||||
get { return _startHead; }
|
||||
set { _startHead = value; }
|
||||
}
|
||||
|
||||
public byte StartSector
|
||||
{
|
||||
get { return _startSector; }
|
||||
set { _startSector = value; }
|
||||
}
|
||||
|
||||
public byte PartitionType
|
||||
{
|
||||
get { return _type; }
|
||||
set { _type = value; }
|
||||
}
|
||||
|
||||
public string FriendlyPartitionType
|
||||
{
|
||||
get { return BiosPartitionTypes.ToString(_type); }
|
||||
}
|
||||
|
||||
public ushort EndCylinder
|
||||
{
|
||||
get { return _endCylinder; }
|
||||
set { _endCylinder = value; }
|
||||
}
|
||||
|
||||
public byte EndHead
|
||||
{
|
||||
get { return _endHead; }
|
||||
set { _endHead = value; }
|
||||
}
|
||||
|
||||
public byte EndSector
|
||||
{
|
||||
get { return _endSector; }
|
||||
set { _endSector = value; }
|
||||
}
|
||||
|
||||
public uint LBAStart
|
||||
{
|
||||
get { return _lbaStart; }
|
||||
set { _lbaStart = value; }
|
||||
}
|
||||
|
||||
public uint LBALength
|
||||
{
|
||||
get { return _lbaLength; }
|
||||
set { _lbaLength = value; }
|
||||
}
|
||||
|
||||
public uint LBAStartAbsolute
|
||||
{
|
||||
get { return _lbaStart + _lbaOffset; }
|
||||
}
|
||||
|
||||
public int Index
|
||||
{
|
||||
get { return _index; }
|
||||
}
|
||||
|
||||
public int CompareTo(BiosPartitionRecord other)
|
||||
{
|
||||
return LBAStartAbsolute.CompareTo(other.LBAStartAbsolute);
|
||||
}
|
||||
|
||||
internal void WriteTo(byte[] buffer, int offset)
|
||||
{
|
||||
buffer[offset] = _status;
|
||||
buffer[offset + 1] = _startHead;
|
||||
buffer[offset + 2] = (byte)((_startSector & 0x3F) | ((_startCylinder >> 2) & 0xC0));
|
||||
buffer[offset + 3] = (byte)_startCylinder;
|
||||
buffer[offset + 4] = _type;
|
||||
buffer[offset + 5] = _endHead;
|
||||
buffer[offset + 6] = (byte)((_endSector & 0x3F) | ((_endCylinder >> 2) & 0xC0));
|
||||
buffer[offset + 7] = (byte)_endCylinder;
|
||||
Utilities.WriteBytesLittleEndian((uint)_lbaStart, buffer, offset + 8);
|
||||
Utilities.WriteBytesLittleEndian((uint)_lbaLength, buffer, offset + 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a BIOS (MBR) Partition Table.
|
||||
/// </summary>
|
||||
public sealed class BiosPartitionTable : PartitionTable
|
||||
{
|
||||
private Stream _diskData;
|
||||
private Geometry _diskGeometry;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the BiosPartitionTable class.
|
||||
/// </summary>
|
||||
/// <param name="disk">The disk containing the partition table.</param>
|
||||
public BiosPartitionTable(VirtualDisk disk)
|
||||
{
|
||||
Init(disk.Content, disk.BiosGeometry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the BiosPartitionTable class.
|
||||
/// </summary>
|
||||
/// <param name="disk">The stream containing the disk data.</param>
|
||||
/// <param name="diskGeometry">The geometry of the disk.</param>
|
||||
public BiosPartitionTable(Stream disk, Geometry diskGeometry)
|
||||
{
|
||||
Init(disk, diskGeometry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GUID that uniquely identifies this disk, if supported (else returns <c>null</c>).
|
||||
/// </summary>
|
||||
public override Guid DiskGuid
|
||||
{
|
||||
get { return Guid.Empty; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the partitions for storing Operating System file-systems.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<BiosPartitionInfo> BiosUserPartitions
|
||||
{
|
||||
get
|
||||
{
|
||||
List<BiosPartitionInfo> result = new List<BiosPartitionInfo>();
|
||||
foreach (BiosPartitionRecord r in GetAllRecords())
|
||||
{
|
||||
if (r.IsValid)
|
||||
{
|
||||
result.Add(new BiosPartitionInfo(this, r));
|
||||
}
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<BiosPartitionInfo>(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the partitions for storing Operating System file-systems.
|
||||
/// </summary>
|
||||
public override ReadOnlyCollection<PartitionInfo> Partitions
|
||||
{
|
||||
get
|
||||
{
|
||||
List<PartitionInfo> result = new List<PartitionInfo>();
|
||||
foreach (BiosPartitionRecord r in GetAllRecords())
|
||||
{
|
||||
if (r.IsValid)
|
||||
{
|
||||
result.Add(new BiosPartitionInfo(this, r));
|
||||
}
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<PartitionInfo>(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes a best guess at the geometry of a disk.
|
||||
/// </summary>
|
||||
/// <param name="disk">String containing the disk image to detect the geometry from.</param>
|
||||
/// <returns>The detected geometry.</returns>
|
||||
public static Geometry DetectGeometry(Stream disk)
|
||||
{
|
||||
if (disk.Length >= Utilities.SectorSize)
|
||||
{
|
||||
disk.Position = 0;
|
||||
byte[] bootSector = Utilities.ReadFully(disk, Utilities.SectorSize);
|
||||
if (bootSector[510] == 0x55 && bootSector[511] == 0xAA)
|
||||
{
|
||||
byte maxHead = 0;
|
||||
byte maxSector = 0;
|
||||
foreach (var record in ReadPrimaryRecords(bootSector))
|
||||
{
|
||||
maxHead = Math.Max(maxHead, record.EndHead);
|
||||
maxSector = Math.Max(maxSector, record.EndSector);
|
||||
}
|
||||
|
||||
if (maxHead > 0 && maxSector > 0)
|
||||
{
|
||||
int cylSize = (maxHead + 1) * maxSector * 512;
|
||||
return new Geometry((int)Utilities.Ceil(disk.Length, cylSize), maxHead + 1, maxSector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Geometry.FromCapacity(disk.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if a stream contains a valid partition table.
|
||||
/// </summary>
|
||||
/// <param name="disk">The stream to inspect.</param>
|
||||
/// <returns><c>true</c> if the partition table is valid, else <c>false</c>.</returns>
|
||||
public static bool IsValid(Stream disk)
|
||||
{
|
||||
if (disk.Length < Utilities.SectorSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
disk.Position = 0;
|
||||
byte[] bootSector = Utilities.ReadFully(disk, Utilities.SectorSize);
|
||||
|
||||
// Check for the 'bootable sector' marker
|
||||
if (bootSector[510] != 0x55 || bootSector[511] != 0xAA)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<StreamExtent> knownPartitions = new List<StreamExtent>();
|
||||
foreach (var record in ReadPrimaryRecords(bootSector))
|
||||
{
|
||||
// If the partition extends beyond the end of the disk, this is probably an invalid partition table
|
||||
if (record.LBALength != 0xFFFFFFFF && (record.LBAStart + (long)record.LBALength) * Sizes.Sector > disk.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (record.LBALength > 0)
|
||||
{
|
||||
StreamExtent[] thisPartitionExtents = new StreamExtent[] { new StreamExtent(record.LBAStart, record.LBALength) };
|
||||
|
||||
// If the partition intersects another partition, this is probably an invalid partition table
|
||||
foreach (var overlap in StreamExtent.Intersect(knownPartitions, thisPartitionExtents))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
knownPartitions = new List<StreamExtent>(StreamExtent.Union(knownPartitions, thisPartitionExtents));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partition table on a disk.
|
||||
/// </summary>
|
||||
/// <param name="disk">The disk to initialize.</param>
|
||||
/// <returns>An object to access the newly created partition table.</returns>
|
||||
public static BiosPartitionTable Initialize(VirtualDisk disk)
|
||||
{
|
||||
return Initialize(disk.Content, disk.BiosGeometry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partition table on a disk containing a single partition.
|
||||
/// </summary>
|
||||
/// <param name="disk">The disk to initialize.</param>
|
||||
/// <param name="type">The partition type for the single partition.</param>
|
||||
/// <returns>An object to access the newly created partition table.</returns>
|
||||
public static BiosPartitionTable Initialize(VirtualDisk disk, WellKnownPartitionType type)
|
||||
{
|
||||
BiosPartitionTable table = Initialize(disk.Content, disk.BiosGeometry);
|
||||
table.Create(type, true);
|
||||
return table;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partition table on a disk.
|
||||
/// </summary>
|
||||
/// <param name="disk">The stream containing the disk data.</param>
|
||||
/// <param name="diskGeometry">The geometry of the disk.</param>
|
||||
/// <returns>An object to access the newly created partition table.</returns>
|
||||
public static BiosPartitionTable Initialize(Stream disk, Geometry diskGeometry)
|
||||
{
|
||||
Stream data = disk;
|
||||
|
||||
byte[] bootSector;
|
||||
if (data.Length >= Utilities.SectorSize)
|
||||
{
|
||||
data.Position = 0;
|
||||
bootSector = Utilities.ReadFully(data, Utilities.SectorSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
bootSector = new byte[Utilities.SectorSize];
|
||||
}
|
||||
|
||||
// Wipe all four 16-byte partition table entries
|
||||
Array.Clear(bootSector, 0x01BE, 16 * 4);
|
||||
|
||||
// Marker bytes
|
||||
bootSector[510] = 0x55;
|
||||
bootSector[511] = 0xAA;
|
||||
|
||||
data.Position = 0;
|
||||
data.Write(bootSector, 0, bootSector.Length);
|
||||
|
||||
return new BiosPartitionTable(disk, diskGeometry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partition that encompasses the entire disk.
|
||||
/// </summary>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <returns>The index of the partition.</returns>
|
||||
/// <remarks>The partition table must be empty before this method is called,
|
||||
/// otherwise IOException is thrown.</remarks>
|
||||
public override int Create(WellKnownPartitionType type, bool active)
|
||||
{
|
||||
Geometry allocationGeometry = new Geometry(_diskData.Length, _diskGeometry.HeadsPerCylinder, _diskGeometry.SectorsPerTrack, _diskGeometry.BytesPerSector);
|
||||
|
||||
ChsAddress start = new ChsAddress(0, 1, 1);
|
||||
ChsAddress last = allocationGeometry.LastSector;
|
||||
|
||||
long startLba = allocationGeometry.ToLogicalBlockAddress(start);
|
||||
long lastLba = allocationGeometry.ToLogicalBlockAddress(last);
|
||||
|
||||
return CreatePrimaryByCylinder(0, allocationGeometry.Cylinders - 1, ConvertType(type, (lastLba - startLba) * Utilities.SectorSize), active);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new primary partition with a target size.
|
||||
/// </summary>
|
||||
/// <param name="size">The target size (in bytes).</param>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <returns>The index of the new partition.</returns>
|
||||
public override int Create(long size, WellKnownPartitionType type, bool active)
|
||||
{
|
||||
int cylinderCapacity = _diskGeometry.SectorsPerTrack * _diskGeometry.HeadsPerCylinder * _diskGeometry.BytesPerSector;
|
||||
int numCylinders = (int)(size / cylinderCapacity);
|
||||
|
||||
int startCylinder = FindCylinderGap(numCylinders);
|
||||
|
||||
return CreatePrimaryByCylinder(startCylinder, startCylinder + numCylinders - 1, ConvertType(type, size), active);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new aligned partition that encompasses the entire disk.
|
||||
/// </summary>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <param name="alignment">The alignment (in bytes).</param>
|
||||
/// <returns>The index of the partition.</returns>
|
||||
/// <remarks>The partition table must be empty before this method is called,
|
||||
/// otherwise IOException is thrown.</remarks>
|
||||
/// <remarks>
|
||||
/// Traditionally partitions were aligned to the physical structure of the underlying disk,
|
||||
/// however with modern storage greater efficiency is acheived by aligning partitions on
|
||||
/// large values that are a power of two.
|
||||
/// </remarks>
|
||||
public override int CreateAligned(WellKnownPartitionType type, bool active, int alignment)
|
||||
{
|
||||
Geometry allocationGeometry = new Geometry(_diskData.Length, _diskGeometry.HeadsPerCylinder, _diskGeometry.SectorsPerTrack, _diskGeometry.BytesPerSector);
|
||||
|
||||
ChsAddress start = new ChsAddress(0, 1, 1);
|
||||
|
||||
long startLba = Utilities.RoundUp(allocationGeometry.ToLogicalBlockAddress(start), alignment / _diskGeometry.BytesPerSector);
|
||||
long lastLba = Utilities.RoundDown(_diskData.Length / _diskGeometry.BytesPerSector, alignment / _diskGeometry.BytesPerSector);
|
||||
|
||||
return CreatePrimaryBySector(startLba, lastLba - 1, ConvertType(type, (lastLba - startLba) * _diskGeometry.BytesPerSector), active);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new aligned partition with a target size.
|
||||
/// </summary>
|
||||
/// <param name="size">The target size (in bytes).</param>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <param name="alignment">The alignment (in bytes).</param>
|
||||
/// <returns>The index of the new partition.</returns>
|
||||
/// <remarks>
|
||||
/// Traditionally partitions were aligned to the physical structure of the underlying disk,
|
||||
/// however with modern storage greater efficiency is achieved by aligning partitions on
|
||||
/// large values that are a power of two.
|
||||
/// </remarks>
|
||||
public override int CreateAligned(long size, WellKnownPartitionType type, bool active, int alignment)
|
||||
{
|
||||
if (size < _diskGeometry.BytesPerSector)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("size", size, "size must be at least one sector");
|
||||
}
|
||||
|
||||
if (alignment % _diskGeometry.BytesPerSector != 0)
|
||||
{
|
||||
throw new ArgumentException("Alignment is not a multiple of the sector size");
|
||||
}
|
||||
|
||||
if (size % alignment != 0)
|
||||
{
|
||||
throw new ArgumentException("Size is not a multiple of the alignment");
|
||||
}
|
||||
|
||||
long sectorLength = size / _diskGeometry.BytesPerSector;
|
||||
long start = FindGap(size / _diskGeometry.BytesPerSector, alignment / _diskGeometry.BytesPerSector);
|
||||
|
||||
return CreatePrimaryBySector(start, start + sectorLength - 1, ConvertType(type, sectorLength * Utilities.SectorSize), active);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a partition at a given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the partition.</param>
|
||||
public override void Delete(int index)
|
||||
{
|
||||
WriteRecord(index, new BiosPartitionRecord());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Primary Partition that occupies whole cylinders, for best compatibility.
|
||||
/// </summary>
|
||||
/// <param name="first">The first cylinder to include in the partition (inclusive).</param>
|
||||
/// <param name="last">The last cylinder to include in the partition (inclusive).</param>
|
||||
/// <param name="type">The BIOS (MBR) type of the new partition.</param>
|
||||
/// <param name="markActive">Whether to mark the partition active (bootable).</param>
|
||||
/// <returns>The index of the new partition.</returns>
|
||||
/// <remarks>If the cylinder 0 is given, the first track will not be used, to reserve space
|
||||
/// for the meta-data at the start of the disk.</remarks>
|
||||
public int CreatePrimaryByCylinder(int first, int last, byte type, bool markActive)
|
||||
{
|
||||
if (first < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("first", first, "First cylinder must be Zero or greater");
|
||||
}
|
||||
|
||||
if (last <= first)
|
||||
{
|
||||
throw new ArgumentException("Last cylinder must be greater than first");
|
||||
}
|
||||
|
||||
long lbaStart = (first == 0) ? _diskGeometry.ToLogicalBlockAddress(0, 1, 1) : _diskGeometry.ToLogicalBlockAddress(first, 0, 1);
|
||||
long lbaLast = _diskGeometry.ToLogicalBlockAddress(last, _diskGeometry.HeadsPerCylinder - 1, _diskGeometry.SectorsPerTrack);
|
||||
|
||||
return CreatePrimaryBySector(lbaStart, lbaLast, type, markActive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Primary Partition, specified by Logical Block Addresses.
|
||||
/// </summary>
|
||||
/// <param name="first">The LBA address of the first sector (inclusive).</param>
|
||||
/// <param name="last">The LBA address of the last sector (inclusive).</param>
|
||||
/// <param name="type">The BIOS (MBR) type of the new partition.</param>
|
||||
/// <param name="markActive">Whether to mark the partition active (bootable).</param>
|
||||
/// <returns>The index of the new partition.</returns>
|
||||
public int CreatePrimaryBySector(long first, long last, byte type, bool markActive)
|
||||
{
|
||||
if (first >= last)
|
||||
{
|
||||
throw new ArgumentException("The first sector in a partition must be before the last");
|
||||
}
|
||||
|
||||
if ((last + 1) * _diskGeometry.BytesPerSector > _diskData.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("last", last, "The last sector extends beyond the end of the disk");
|
||||
}
|
||||
|
||||
BiosPartitionRecord[] existing = GetPrimaryRecords();
|
||||
|
||||
BiosPartitionRecord newRecord = new BiosPartitionRecord();
|
||||
ChsAddress startAddr = _diskGeometry.ToChsAddress(first);
|
||||
ChsAddress endAddr = _diskGeometry.ToChsAddress(last);
|
||||
|
||||
// Because C/H/S addresses can max out at lower values than the LBA values,
|
||||
// the special tuple (1023, 254, 63) is used.
|
||||
if (startAddr.Cylinder > 1023)
|
||||
{
|
||||
startAddr = new ChsAddress(1023, 254, 63);
|
||||
}
|
||||
|
||||
if (endAddr.Cylinder > 1023)
|
||||
{
|
||||
endAddr = new ChsAddress(1023, 254, 63);
|
||||
}
|
||||
|
||||
newRecord.StartCylinder = (ushort)startAddr.Cylinder;
|
||||
newRecord.StartHead = (byte)startAddr.Head;
|
||||
newRecord.StartSector = (byte)startAddr.Sector;
|
||||
newRecord.EndCylinder = (ushort)endAddr.Cylinder;
|
||||
newRecord.EndHead = (byte)endAddr.Head;
|
||||
newRecord.EndSector = (byte)endAddr.Sector;
|
||||
newRecord.LBAStart = (uint)first;
|
||||
newRecord.LBALength = (uint)(last - first + 1);
|
||||
newRecord.PartitionType = type;
|
||||
newRecord.Status = (byte)(markActive ? 0x80 : 0x00);
|
||||
|
||||
// First check for overlap with existing partition...
|
||||
foreach (var r in existing)
|
||||
{
|
||||
if (Utilities.RangesOverlap((uint)first, (uint)last + 1, r.LBAStartAbsolute, r.LBAStartAbsolute + r.LBALength))
|
||||
{
|
||||
throw new IOException("New partition overlaps with existing partition");
|
||||
}
|
||||
}
|
||||
|
||||
// Now look for empty partition
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (!existing[i].IsValid)
|
||||
{
|
||||
WriteRecord(i, newRecord);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("No primary partition slots available");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the active partition.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the primary partition to mark bootable, or <c>-1</c> for none.</param>
|
||||
/// <remarks>The supplied index is the index within the primary partition, see <c>PrimaryIndex</c> on <c>BiosPartitionInfo</c>.</remarks>
|
||||
public void SetActivePartition(int index)
|
||||
{
|
||||
List<BiosPartitionRecord> records = new List<BiosPartitionRecord>(GetPrimaryRecords());
|
||||
|
||||
for (int i = 0; i < records.Count; ++i)
|
||||
{
|
||||
records[i].Status = (i == index) ? (byte)0x80 : (byte)0x00;
|
||||
WriteRecord(i, records[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all of the disk ranges containing partition table metadata.
|
||||
/// </summary>
|
||||
/// <returns>Set of stream extents, indicated as byte offset from the start of the disk.</returns>
|
||||
public IEnumerable<StreamExtent> GetMetadataDiskExtents()
|
||||
{
|
||||
List<StreamExtent> extents = new List<StreamExtent>();
|
||||
|
||||
extents.Add(new StreamExtent(0, Sizes.Sector));
|
||||
|
||||
foreach (BiosPartitionRecord primaryRecord in GetPrimaryRecords())
|
||||
{
|
||||
if (primaryRecord.IsValid)
|
||||
{
|
||||
if (IsExtendedPartition(primaryRecord))
|
||||
{
|
||||
extents.AddRange(new BiosExtendedPartitionTable(_diskData, primaryRecord.LBAStart).GetMetadataDiskExtents());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extents;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the CHS fields in partition records to reflect a new BIOS geometry.
|
||||
/// </summary>
|
||||
/// <param name="geometry">The disk's new BIOS geometry.</param>
|
||||
/// <remarks>The partitions are not relocated to a cylinder boundary, just the CHS fields are updated on the
|
||||
/// assumption the LBA fields are definitive.</remarks>
|
||||
public void UpdateBiosGeometry(Geometry geometry)
|
||||
{
|
||||
_diskData.Position = 0;
|
||||
byte[] bootSector = Utilities.ReadFully(_diskData, Utilities.SectorSize);
|
||||
|
||||
BiosPartitionRecord[] records = ReadPrimaryRecords(bootSector);
|
||||
for (int i = 0; i < records.Length; ++i)
|
||||
{
|
||||
BiosPartitionRecord record = records[i];
|
||||
if (record.IsValid)
|
||||
{
|
||||
ChsAddress newStartAddress = geometry.ToChsAddress(record.LBAStartAbsolute);
|
||||
if (newStartAddress.Cylinder > 1023)
|
||||
{
|
||||
newStartAddress = new ChsAddress(1023, geometry.HeadsPerCylinder - 1, geometry.SectorsPerTrack);
|
||||
}
|
||||
|
||||
ChsAddress newEndAddress = geometry.ToChsAddress(record.LBAStartAbsolute + record.LBALength - 1);
|
||||
if (newEndAddress.Cylinder > 1023)
|
||||
{
|
||||
newEndAddress = new ChsAddress(1023, geometry.HeadsPerCylinder - 1, geometry.SectorsPerTrack);
|
||||
}
|
||||
|
||||
record.StartCylinder = (ushort)newStartAddress.Cylinder;
|
||||
record.StartHead = (byte)newStartAddress.Head;
|
||||
record.StartSector = (byte)newStartAddress.Sector;
|
||||
record.EndCylinder = (ushort)newEndAddress.Cylinder;
|
||||
record.EndHead = (byte)newEndAddress.Head;
|
||||
record.EndSector = (byte)newEndAddress.Sector;
|
||||
|
||||
WriteRecord(i, record);
|
||||
}
|
||||
}
|
||||
|
||||
_diskGeometry = geometry;
|
||||
}
|
||||
|
||||
internal SparseStream Open(BiosPartitionRecord record)
|
||||
{
|
||||
return new SubStream(_diskData, Ownership.None, ((long)record.LBAStartAbsolute) * _diskGeometry.BytesPerSector, ((long)record.LBALength) * _diskGeometry.BytesPerSector);
|
||||
}
|
||||
|
||||
private static BiosPartitionRecord[] ReadPrimaryRecords(byte[] bootSector)
|
||||
{
|
||||
BiosPartitionRecord[] records = new BiosPartitionRecord[4];
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
records[i] = new BiosPartitionRecord(bootSector, 0x01BE + (i * 0x10), 0, i);
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
private static bool IsExtendedPartition(BiosPartitionRecord r)
|
||||
{
|
||||
return r.PartitionType == BiosPartitionTypes.Extended || r.PartitionType == BiosPartitionTypes.ExtendedLba;
|
||||
}
|
||||
|
||||
private static byte ConvertType(WellKnownPartitionType type, long size)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case WellKnownPartitionType.WindowsFat:
|
||||
if (size < 512 * Sizes.OneMiB)
|
||||
{
|
||||
return BiosPartitionTypes.Fat16;
|
||||
}
|
||||
else if (size < 1023 * (long)254 * 63 * 512)
|
||||
{
|
||||
// Max BIOS size
|
||||
return BiosPartitionTypes.Fat32;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BiosPartitionTypes.Fat32Lba;
|
||||
}
|
||||
|
||||
case WellKnownPartitionType.WindowsNtfs:
|
||||
return BiosPartitionTypes.Ntfs;
|
||||
case WellKnownPartitionType.Linux:
|
||||
return BiosPartitionTypes.LinuxNative;
|
||||
case WellKnownPartitionType.LinuxSwap:
|
||||
return BiosPartitionTypes.LinuxSwap;
|
||||
case WellKnownPartitionType.LinuxLvm:
|
||||
return BiosPartitionTypes.LinuxLvm;
|
||||
default:
|
||||
throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "Unrecognized partition type: '{0}'", type), "type");
|
||||
}
|
||||
}
|
||||
|
||||
private BiosPartitionRecord[] GetAllRecords()
|
||||
{
|
||||
List<BiosPartitionRecord> newList = new List<BiosPartitionRecord>();
|
||||
|
||||
foreach (BiosPartitionRecord primaryRecord in GetPrimaryRecords())
|
||||
{
|
||||
if (primaryRecord.IsValid)
|
||||
{
|
||||
if (IsExtendedPartition(primaryRecord))
|
||||
{
|
||||
newList.AddRange(GetExtendedRecords(primaryRecord));
|
||||
}
|
||||
else
|
||||
{
|
||||
newList.Add(primaryRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newList.ToArray();
|
||||
}
|
||||
|
||||
private BiosPartitionRecord[] GetPrimaryRecords()
|
||||
{
|
||||
_diskData.Position = 0;
|
||||
byte[] bootSector = Utilities.ReadFully(_diskData, Utilities.SectorSize);
|
||||
|
||||
return ReadPrimaryRecords(bootSector);
|
||||
}
|
||||
|
||||
private BiosPartitionRecord[] GetExtendedRecords(BiosPartitionRecord r)
|
||||
{
|
||||
return new BiosExtendedPartitionTable(_diskData, r.LBAStart).GetPartitions();
|
||||
}
|
||||
|
||||
private void WriteRecord(int i, BiosPartitionRecord newRecord)
|
||||
{
|
||||
_diskData.Position = 0;
|
||||
byte[] bootSector = Utilities.ReadFully(_diskData, Utilities.SectorSize);
|
||||
newRecord.WriteTo(bootSector, 0x01BE + (i * 16));
|
||||
_diskData.Position = 0;
|
||||
_diskData.Write(bootSector, 0, bootSector.Length);
|
||||
}
|
||||
|
||||
private int FindCylinderGap(int numCylinders)
|
||||
{
|
||||
var list = Utilities.Filter<List<BiosPartitionRecord>, BiosPartitionRecord>(GetPrimaryRecords(), (r) => r.IsValid);
|
||||
list.Sort();
|
||||
|
||||
int startCylinder = 0;
|
||||
foreach (var r in list)
|
||||
{
|
||||
int existingStart = r.StartCylinder;
|
||||
int existingEnd = r.EndCylinder;
|
||||
|
||||
// LBA can represent bigger disk locations than CHS, so assume the LBA to be definitive in the case where it
|
||||
// appears the CHS address has been truncated.
|
||||
if (r.LBAStart > _diskGeometry.ToLogicalBlockAddress(r.StartCylinder, r.StartHead, r.StartSector))
|
||||
{
|
||||
existingStart = _diskGeometry.ToChsAddress((int)r.LBAStart).Cylinder;
|
||||
}
|
||||
|
||||
if (r.LBAStart + r.LBALength > _diskGeometry.ToLogicalBlockAddress(r.EndCylinder, r.EndHead, r.EndSector))
|
||||
{
|
||||
existingEnd = _diskGeometry.ToChsAddress((int)(r.LBAStart + r.LBALength)).Cylinder;
|
||||
}
|
||||
|
||||
if (!Utilities.RangesOverlap(startCylinder, startCylinder + numCylinders - 1, existingStart, existingEnd))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
startCylinder = existingEnd + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return startCylinder;
|
||||
}
|
||||
|
||||
private long FindGap(long numSectors, long alignmentSectors)
|
||||
{
|
||||
var list = Utilities.Filter<List<BiosPartitionRecord>, BiosPartitionRecord>(GetPrimaryRecords(), (r) => r.IsValid);
|
||||
list.Sort();
|
||||
|
||||
long startSector = Utilities.RoundUp(_diskGeometry.ToLogicalBlockAddress(0, 1, 1), alignmentSectors);
|
||||
|
||||
int idx = 0;
|
||||
while (idx < list.Count)
|
||||
{
|
||||
var entry = list[idx];
|
||||
while (idx < list.Count && startSector >= entry.LBAStartAbsolute + entry.LBALength)
|
||||
{
|
||||
idx++;
|
||||
entry = list[idx];
|
||||
}
|
||||
|
||||
if (Utilities.RangesOverlap(startSector, startSector + numSectors, entry.LBAStartAbsolute, entry.LBAStartAbsolute + entry.LBALength))
|
||||
{
|
||||
startSector = Utilities.RoundUp(entry.LBAStartAbsolute + entry.LBALength, alignmentSectors);
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (_diskGeometry.TotalSectorsLong - startSector < numSectors)
|
||||
{
|
||||
throw new IOException(string.Format(CultureInfo.InvariantCulture, "Unable to find free space of {0} sectors", numSectors));
|
||||
}
|
||||
|
||||
return startSector;
|
||||
}
|
||||
|
||||
private void Init(Stream disk, Geometry diskGeometry)
|
||||
{
|
||||
_diskData = disk;
|
||||
_diskGeometry = diskGeometry;
|
||||
|
||||
_diskData.Position = 0;
|
||||
byte[] bootSector = Utilities.ReadFully(_diskData, Utilities.SectorSize);
|
||||
if (bootSector[510] != 0x55 || bootSector[511] != 0xAA)
|
||||
{
|
||||
throw new IOException("Invalid boot sector - no magic number 0xAA55");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
/// <summary>
|
||||
/// Convenient access to well-known BIOS (MBR) Partition Types.
|
||||
/// </summary>
|
||||
public static class BiosPartitionTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Microsoft FAT12 (fewer than 32,680 sectors in the volume).
|
||||
/// </summary>
|
||||
public const byte Fat12 = 0x01;
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft FAT16 (32,680–65,535 sectors or 16 MB–33 MB).
|
||||
/// </summary>
|
||||
public const byte Fat16Small = 0x04;
|
||||
|
||||
/// <summary>
|
||||
/// Extended Partition (contains other partitions).
|
||||
/// </summary>
|
||||
public const byte Extended = 0x05;
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft BIGDOS FAT16 (33 MB–4 GB).
|
||||
/// </summary>
|
||||
public const byte Fat16 = 0x06;
|
||||
|
||||
/// <summary>
|
||||
/// Installable File System (NTFS).
|
||||
/// </summary>
|
||||
public const byte Ntfs = 0x07;
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft FAT32.
|
||||
/// </summary>
|
||||
public const byte Fat32 = 0x0B;
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft FAT32, accessed using Int13h BIOS LBA extensions.
|
||||
/// </summary>
|
||||
public const byte Fat32Lba = 0x0C;
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft BIGDOS FAT16, accessed using Int13h BIOS LBA extensions.
|
||||
/// </summary>
|
||||
public const byte Fat16Lba = 0x0E;
|
||||
|
||||
/// <summary>
|
||||
/// Extended Partition (contains other partitions), accessed using Int13h BIOS LBA extensions.
|
||||
/// </summary>
|
||||
public const byte ExtendedLba = 0x0F;
|
||||
|
||||
/// <summary>
|
||||
/// Windows Logical Disk Manager dynamic volume.
|
||||
/// </summary>
|
||||
public const byte WindowsDynamicVolume = 0x42;
|
||||
|
||||
/// <summary>
|
||||
/// Linux Swap.
|
||||
/// </summary>
|
||||
public const byte LinuxSwap = 0x82;
|
||||
|
||||
/// <summary>
|
||||
/// Linux Native (ext2 and friends).
|
||||
/// </summary>
|
||||
public const byte LinuxNative = 0x83;
|
||||
|
||||
/// <summary>
|
||||
/// Linux Logical Volume Manager (LVM).
|
||||
/// </summary>
|
||||
public const byte LinuxLvm = 0x8E;
|
||||
|
||||
/// <summary>
|
||||
/// GUID Partition Table (GPT) protective partition, fills entire disk.
|
||||
/// </summary>
|
||||
public const byte GptProtective = 0xEE;
|
||||
|
||||
/// <summary>
|
||||
/// EFI System partition on an MBR disk.
|
||||
/// </summary>
|
||||
public const byte EfiSystem = 0xEF;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a string representation of some known BIOS partition types.
|
||||
/// </summary>
|
||||
/// <param name="type">The partition type to represent as a string.</param>
|
||||
/// <returns>The string representation.</returns>
|
||||
public static string ToString(byte type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case 0x00: return "Unused";
|
||||
case 0x01: return "FAT12";
|
||||
case 0x02: return "XENIX root";
|
||||
case 0x03: return "XENIX /usr";
|
||||
case 0x04: return "FAT16 (<32M)";
|
||||
case 0x05: return "Extended (non-LBA)";
|
||||
case 0x06: return "FAT16 (>32M)";
|
||||
case 0x07: return "IFS (NTFS or HPFS)";
|
||||
case 0x0B: return "FAT32 (non-LBA)";
|
||||
case 0x0C: return "FAT32 (LBA)";
|
||||
case 0x0E: return "FAT16 (LBA)";
|
||||
case 0x0F: return "Extended (LBA)";
|
||||
case 0x11: return "Hidden FAT12";
|
||||
case 0x12: return "Vendor Config/Recovery/Diagnostics";
|
||||
case 0x14: return "Hidden FAT16 (<32M)";
|
||||
case 0x16: return "Hidden FAT16 (>32M)";
|
||||
case 0x17: return "Hidden IFS (NTFS or HPFS)";
|
||||
case 0x1B: return "Hidden FAT32 (non-LBA)";
|
||||
case 0x1C: return "Hidden FAT32 (LBA)";
|
||||
case 0x1E: return "Hidden FAT16 (LBA)";
|
||||
case 0x27: return "Windows Recovery Environment";
|
||||
case 0x42: return "Windows Dynamic Volume";
|
||||
case 0x80: return "Minix v1.1 - v1.4a";
|
||||
case 0x81: return "Minix / Early Linux";
|
||||
case 0x82: return "Linux Swap";
|
||||
case 0x83: return "Linux Native";
|
||||
case 0x84: return "Hibernation";
|
||||
case 0x8E: return "Linux LVM";
|
||||
case 0xA0: return "Laptop Hibernation";
|
||||
case 0xA8: return "Mac OS-X";
|
||||
case 0xAB: return "Mac OS-X Boot";
|
||||
case 0xAF: return "Mac OS-X HFS";
|
||||
case 0xC0: return "NTFT";
|
||||
case 0xDE: return "Dell OEM";
|
||||
case 0xEE: return "GPT Protective";
|
||||
case 0xEF: return "EFI";
|
||||
case 0xFB: return "VMware File System";
|
||||
case 0xFC: return "VMware Swap";
|
||||
case 0xFE: return "IBM OEM";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Builds a stream with the contents of a BIOS partitioned disk.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class assembles a disk image dynamically in memory. The
|
||||
/// constructed stream will read data from the partition content
|
||||
/// streams only when a client of this class tries to read from
|
||||
/// that partition.
|
||||
/// </remarks>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "SparseMemoryStream holds no resources")]
|
||||
public class BiosPartitionedDiskBuilder : StreamBuilder
|
||||
{
|
||||
private long _capacity;
|
||||
private BiosPartitionTable _partitionTable;
|
||||
private Geometry _biosGeometry;
|
||||
private SparseMemoryStream _bootSectors;
|
||||
|
||||
private Dictionary<int, BuilderExtent> _partitionContents;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the BiosPartitionedDiskBuilder class.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The capacity of the disk (in bytes).</param>
|
||||
/// <param name="biosGeometry">The BIOS geometry of the disk.</param>
|
||||
public BiosPartitionedDiskBuilder(long capacity, Geometry biosGeometry)
|
||||
{
|
||||
_capacity = capacity;
|
||||
_biosGeometry = biosGeometry;
|
||||
|
||||
_bootSectors = new SparseMemoryStream();
|
||||
_bootSectors.SetLength(capacity);
|
||||
_partitionTable = BiosPartitionTable.Initialize(_bootSectors, _biosGeometry);
|
||||
|
||||
_partitionContents = new Dictionary<int, BuilderExtent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the BiosPartitionedDiskBuilder class.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The capacity of the disk (in bytes).</param>
|
||||
/// <param name="bootSectors">The boot sector(s) of the disk.</param>
|
||||
/// <param name="biosGeometry">The BIOS geometry of the disk.</param>
|
||||
[Obsolete("Use the variant that takes VirtualDisk, this method breaks for disks with extended partitions", false)]
|
||||
public BiosPartitionedDiskBuilder(long capacity, byte[] bootSectors, Geometry biosGeometry)
|
||||
{
|
||||
if (bootSectors == null)
|
||||
{
|
||||
throw new ArgumentNullException("bootSectors");
|
||||
}
|
||||
|
||||
_capacity = capacity;
|
||||
_biosGeometry = biosGeometry;
|
||||
|
||||
_bootSectors = new SparseMemoryStream();
|
||||
_bootSectors.SetLength(capacity);
|
||||
_bootSectors.Write(bootSectors, 0, bootSectors.Length);
|
||||
_partitionTable = new BiosPartitionTable(_bootSectors, biosGeometry);
|
||||
|
||||
_partitionContents = new Dictionary<int, BuilderExtent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the BiosPartitionedDiskBuilder class by
|
||||
/// cloning the partition structure of a source disk.
|
||||
/// </summary>
|
||||
/// <param name="sourceDisk">The disk to clone.</param>
|
||||
public BiosPartitionedDiskBuilder(VirtualDisk sourceDisk)
|
||||
{
|
||||
if (sourceDisk == null)
|
||||
{
|
||||
throw new ArgumentNullException("sourceDisk");
|
||||
}
|
||||
|
||||
_capacity = sourceDisk.Capacity;
|
||||
_biosGeometry = sourceDisk.BiosGeometry;
|
||||
|
||||
_bootSectors = new SparseMemoryStream();
|
||||
_bootSectors.SetLength(_capacity);
|
||||
|
||||
foreach (var extent in new BiosPartitionTable(sourceDisk).GetMetadataDiskExtents())
|
||||
{
|
||||
sourceDisk.Content.Position = extent.Start;
|
||||
byte[] buffer = Utilities.ReadFully(sourceDisk.Content, (int)extent.Length);
|
||||
_bootSectors.Position = extent.Start;
|
||||
_bootSectors.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
_partitionTable = new BiosPartitionTable(_bootSectors, _biosGeometry);
|
||||
|
||||
_partitionContents = new Dictionary<int, BuilderExtent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the partition table in the disk.
|
||||
/// </summary>
|
||||
public BiosPartitionTable PartitionTable
|
||||
{
|
||||
get { return _partitionTable; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a stream representing the content of a partition in the partition table.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the partition.</param>
|
||||
/// <param name="stream">The stream with the contents of the partition.</param>
|
||||
public void SetPartitionContent(int index, SparseStream stream)
|
||||
{
|
||||
_partitionContents[index] = new BuilderSparseStreamExtent(_partitionTable[index].FirstSector * Sizes.Sector, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the CHS fields in partition records to reflect a new BIOS geometry.
|
||||
/// </summary>
|
||||
/// <param name="geometry">The disk's new BIOS geometry.</param>
|
||||
/// <remarks>The partitions are not relocated to a cylinder boundary, just the CHS fields are updated on the
|
||||
/// assumption the LBA fields are definitive.</remarks>
|
||||
public void UpdateBiosGeometry(Geometry geometry)
|
||||
{
|
||||
_partitionTable.UpdateBiosGeometry(geometry);
|
||||
_biosGeometry = geometry;
|
||||
}
|
||||
|
||||
internal override List<BuilderExtent> FixExtents(out long totalLength)
|
||||
{
|
||||
totalLength = _capacity;
|
||||
|
||||
List<BuilderExtent> extents = new List<BuilderExtent>();
|
||||
|
||||
foreach (var extent in _partitionTable.GetMetadataDiskExtents())
|
||||
{
|
||||
_bootSectors.Position = extent.Start;
|
||||
byte[] buffer = Utilities.ReadFully(_bootSectors, (int)extent.Length);
|
||||
|
||||
extents.Add(new BuilderBufferExtent(extent.Start, buffer));
|
||||
}
|
||||
|
||||
extents.AddRange(_partitionContents.Values);
|
||||
return extents;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
[PartitionTableFactory]
|
||||
internal sealed class DefaultPartitionTableFactory : PartitionTableFactory
|
||||
{
|
||||
public override bool DetectIsPartitioned(Stream s)
|
||||
{
|
||||
return BiosPartitionTable.IsValid(s);
|
||||
}
|
||||
|
||||
public override PartitionTable DetectPartitionTable(VirtualDisk disk)
|
||||
{
|
||||
if (BiosPartitionTable.IsValid(disk.Content))
|
||||
{
|
||||
BiosPartitionTable table = new BiosPartitionTable(disk);
|
||||
if (table.Count == 1 && table[0].BiosType == BiosPartitionTypes.GptProtective)
|
||||
{
|
||||
return new GuidPartitionTable(disk);
|
||||
}
|
||||
else
|
||||
{
|
||||
return table;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
internal class GptEntry : IComparable<GptEntry>
|
||||
{
|
||||
public Guid PartitionType;
|
||||
public Guid Identity;
|
||||
public long FirstUsedLogicalBlock;
|
||||
public long LastUsedLogicalBlock;
|
||||
public ulong Attributes;
|
||||
public string Name;
|
||||
|
||||
public GptEntry()
|
||||
{
|
||||
PartitionType = Guid.Empty;
|
||||
Identity = Guid.Empty;
|
||||
Name = string.Empty;
|
||||
}
|
||||
|
||||
public string FriendlyPartitionType
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (PartitionType.ToString().ToUpperInvariant())
|
||||
{
|
||||
case "00000000-0000-0000-0000-000000000000": return "Unused";
|
||||
case "024DEE41-33E7-11D3-9D69-0008C781F39F": return "MBR Partition Scheme";
|
||||
case "C12A7328-F81F-11D2-BA4B-00A0C93EC93B": return "EFI System";
|
||||
case "21686148-6449-6E6F-744E-656564454649": return "BIOS Boot";
|
||||
case "E3C9E316-0B5C-4DB8-817D-F92DF00215AE": return "Microsoft Reserved";
|
||||
case "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7": return "Windows Basic Data";
|
||||
case "5808C8AA-7E8F-42E0-85D2-E1E90434CFB3": return "Windows Logical Disk Manager Metadata";
|
||||
case "AF9B60A0-1431-4F62-BC68-3311714A69AD": return "Windows Logical Disk Manager Data";
|
||||
case "75894C1E-3AEB-11D3-B7C1-7B03A0000000": return "HP-UX Data";
|
||||
case "E2A1E728-32E3-11D6-A682-7B03A0000000": return "HP-UX Service";
|
||||
case "A19D880F-05FC-4D3B-A006-743F0F84911E": return "Linux RAID";
|
||||
case "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F": return "Linux Swap";
|
||||
case "E6D6D379-F507-44C2-A23C-238F2A3DF928": return "Linux Logical Volume Manager";
|
||||
case "83BD6B9D-7F41-11DC-BE0B-001560B84F0F": return "FreeBSD Boot";
|
||||
case "516E7CB4-6ECF-11D6-8FF8-00022D09712B": return "FreeBSD Data";
|
||||
case "516E7CB5-6ECF-11D6-8FF8-00022D09712B": return "FreeBSD Swap";
|
||||
case "516E7CB6-6ECF-11D6-8FF8-00022D09712B": return "FreeBSD Unix File System";
|
||||
case "516E7CB8-6ECF-11D6-8FF8-00022D09712B": return "FreeBSD Vinum volume manager";
|
||||
case "516E7CBA-6ECF-11D6-8FF8-00022D09712B": return "FreeBSD ZFS";
|
||||
case "48465300-0000-11AA-AA11-00306543ECAC": return "Mac OS X HFS+";
|
||||
case "55465300-0000-11AA-AA11-00306543ECAC": return "Mac OS X UFS";
|
||||
case "6A898CC3-1DD2-11B2-99A6-080020736631": return "Mac OS X ZFS";
|
||||
case "52414944-0000-11AA-AA11-00306543ECAC": return "Mac OS X RAID";
|
||||
case "52414944-5F4F-11AA-AA11-00306543ECAC": return "Mac OS X RAID, Offline";
|
||||
case "426F6F74-0000-11AA-AA11-00306543ECAC": return "Mac OS X Boot";
|
||||
case "4C616265-6C00-11AA-AA11-00306543ECAC": return "Mac OS X Label";
|
||||
case "49F48D32-B10E-11DC-B99B-0019D1879648": return "NetBSD Swap";
|
||||
case "49F48D5A-B10E-11DC-B99B-0019D1879648": return "NetBSD Fast File System";
|
||||
case "49F48D82-B10E-11DC-B99B-0019D1879648": return "NetBSD Log-Structed File System";
|
||||
case "49F48DAA-B10E-11DC-B99B-0019D1879648": return "NetBSD RAID";
|
||||
case "2DB519C4-B10F-11DC-B99B-0019D1879648": return "NetBSD Concatenated";
|
||||
case "2DB519EC-B10F-11DC-B99B-0019D1879648": return "NetBSD Encrypted";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadFrom(byte[] buffer, int offset)
|
||||
{
|
||||
PartitionType = Utilities.ToGuidLittleEndian(buffer, offset + 0);
|
||||
Identity = Utilities.ToGuidLittleEndian(buffer, offset + 16);
|
||||
FirstUsedLogicalBlock = Utilities.ToInt64LittleEndian(buffer, offset + 32);
|
||||
LastUsedLogicalBlock = Utilities.ToInt64LittleEndian(buffer, offset + 40);
|
||||
Attributes = Utilities.ToUInt64LittleEndian(buffer, offset + 48);
|
||||
Name = Encoding.Unicode.GetString(buffer, offset + 56, 72).TrimEnd(new char[] { '\0' });
|
||||
}
|
||||
|
||||
public void WriteTo(byte[] buffer, int offset)
|
||||
{
|
||||
Utilities.WriteBytesLittleEndian(PartitionType, buffer, offset + 0);
|
||||
Utilities.WriteBytesLittleEndian(Identity, buffer, offset + 16);
|
||||
Utilities.WriteBytesLittleEndian(FirstUsedLogicalBlock, buffer, offset + 32);
|
||||
Utilities.WriteBytesLittleEndian(LastUsedLogicalBlock, buffer, offset + 40);
|
||||
Utilities.WriteBytesLittleEndian(Attributes, buffer, offset + 48);
|
||||
Encoding.Unicode.GetBytes(Name + new string('\0', 36), 0, 36, buffer, offset + 56);
|
||||
}
|
||||
|
||||
public int CompareTo(GptEntry other)
|
||||
{
|
||||
return FirstUsedLogicalBlock.CompareTo(other.FirstUsedLogicalBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
|
||||
internal class GptHeader
|
||||
{
|
||||
public const string GptSignature = "EFI PART";
|
||||
|
||||
public string Signature;
|
||||
public uint Version;
|
||||
public int HeaderSize;
|
||||
public uint Crc;
|
||||
public long HeaderLba;
|
||||
public long AlternateHeaderLba;
|
||||
public long FirstUsable;
|
||||
public long LastUsable;
|
||||
public Guid DiskGuid;
|
||||
public long PartitionEntriesLba;
|
||||
public uint PartitionEntryCount;
|
||||
public int PartitionEntrySize;
|
||||
public uint EntriesCrc;
|
||||
|
||||
public byte[] Buffer;
|
||||
|
||||
public GptHeader(int sectorSize)
|
||||
{
|
||||
Signature = GptSignature;
|
||||
Version = 0x00010000;
|
||||
HeaderSize = 92;
|
||||
Buffer = new byte[sectorSize];
|
||||
}
|
||||
|
||||
public GptHeader(GptHeader toCopy)
|
||||
{
|
||||
Signature = toCopy.Signature;
|
||||
Version = toCopy.Version;
|
||||
HeaderSize = toCopy.HeaderSize;
|
||||
Crc = toCopy.Crc;
|
||||
HeaderLba = toCopy.HeaderLba;
|
||||
AlternateHeaderLba = toCopy.AlternateHeaderLba;
|
||||
FirstUsable = toCopy.FirstUsable;
|
||||
LastUsable = toCopy.LastUsable;
|
||||
DiskGuid = toCopy.DiskGuid;
|
||||
PartitionEntriesLba = toCopy.PartitionEntriesLba;
|
||||
PartitionEntryCount = toCopy.PartitionEntryCount;
|
||||
PartitionEntrySize = toCopy.PartitionEntrySize;
|
||||
EntriesCrc = toCopy.EntriesCrc;
|
||||
|
||||
Buffer = new byte[toCopy.Buffer.Length];
|
||||
Array.Copy(toCopy.Buffer, Buffer, Buffer.Length);
|
||||
}
|
||||
|
||||
public bool ReadFrom(byte[] buffer, int offset)
|
||||
{
|
||||
Signature = Utilities.BytesToString(buffer, offset + 0, 8);
|
||||
Version = Utilities.ToUInt32LittleEndian(buffer, offset + 8);
|
||||
HeaderSize = Utilities.ToInt32LittleEndian(buffer, offset + 12);
|
||||
Crc = Utilities.ToUInt32LittleEndian(buffer, offset + 16);
|
||||
HeaderLba = Utilities.ToInt64LittleEndian(buffer, offset + 24);
|
||||
AlternateHeaderLba = Utilities.ToInt64LittleEndian(buffer, offset + 32);
|
||||
FirstUsable = Utilities.ToInt64LittleEndian(buffer, offset + 40);
|
||||
LastUsable = Utilities.ToInt64LittleEndian(buffer, offset + 48);
|
||||
DiskGuid = Utilities.ToGuidLittleEndian(buffer, offset + 56);
|
||||
PartitionEntriesLba = Utilities.ToInt64LittleEndian(buffer, offset + 72);
|
||||
PartitionEntryCount = Utilities.ToUInt32LittleEndian(buffer, offset + 80);
|
||||
PartitionEntrySize = Utilities.ToInt32LittleEndian(buffer, offset + 84);
|
||||
EntriesCrc = Utilities.ToUInt32LittleEndian(buffer, offset + 88);
|
||||
|
||||
// In case the header has new fields unknown to us, store the entire header
|
||||
// as a byte array
|
||||
Buffer = new byte[HeaderSize];
|
||||
Array.Copy(buffer, offset, Buffer, 0, HeaderSize);
|
||||
|
||||
// Reject obviously invalid data
|
||||
if (Signature != GptSignature || HeaderSize == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Crc == CalcCrc(buffer, offset, HeaderSize);
|
||||
}
|
||||
|
||||
public void WriteTo(byte[] buffer, int offset)
|
||||
{
|
||||
// First, copy the cached header to allow for unknown fields
|
||||
Array.Copy(Buffer, 0, buffer, offset, Buffer.Length);
|
||||
|
||||
// Next, write the fields
|
||||
Utilities.StringToBytes(Signature, buffer, offset + 0, 8);
|
||||
Utilities.WriteBytesLittleEndian(Version, buffer, offset + 8);
|
||||
Utilities.WriteBytesLittleEndian(HeaderSize, buffer, offset + 12);
|
||||
Utilities.WriteBytesLittleEndian((uint)0, buffer, offset + 16);
|
||||
Utilities.WriteBytesLittleEndian(HeaderLba, buffer, offset + 24);
|
||||
Utilities.WriteBytesLittleEndian(AlternateHeaderLba, buffer, offset + 32);
|
||||
Utilities.WriteBytesLittleEndian(FirstUsable, buffer, offset + 40);
|
||||
Utilities.WriteBytesLittleEndian(LastUsable, buffer, offset + 48);
|
||||
Utilities.WriteBytesLittleEndian(DiskGuid, buffer, offset + 56);
|
||||
Utilities.WriteBytesLittleEndian(PartitionEntriesLba, buffer, offset + 72);
|
||||
Utilities.WriteBytesLittleEndian(PartitionEntryCount, buffer, offset + 80);
|
||||
Utilities.WriteBytesLittleEndian(PartitionEntrySize, buffer, offset + 84);
|
||||
Utilities.WriteBytesLittleEndian(EntriesCrc, buffer, offset + 88);
|
||||
|
||||
// Calculate & write the CRC
|
||||
Utilities.WriteBytesLittleEndian(CalcCrc(buffer, offset, HeaderSize), buffer, offset + 16);
|
||||
|
||||
// Update the cached copy - re-allocate the buffer to allow for HeaderSize potentially having changed
|
||||
Buffer = new byte[HeaderSize];
|
||||
Array.Copy(buffer, offset, Buffer, 0, HeaderSize);
|
||||
}
|
||||
|
||||
internal static uint CalcCrc(byte[] buffer, int offset, int count)
|
||||
{
|
||||
byte[] temp = new byte[count];
|
||||
Array.Copy(buffer, offset, temp, 0, count);
|
||||
|
||||
// Reset CRC field
|
||||
Utilities.WriteBytesLittleEndian((uint)0, temp, 16);
|
||||
|
||||
return Crc32LittleEndian.Compute(Crc32Algorithm.Common, temp, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to partition records in a GUID partition table.
|
||||
/// </summary>
|
||||
public sealed class GuidPartitionInfo : PartitionInfo
|
||||
{
|
||||
private GuidPartitionTable _table;
|
||||
private GptEntry _entry;
|
||||
|
||||
internal GuidPartitionInfo(GuidPartitionTable table, GptEntry entry)
|
||||
{
|
||||
_table = table;
|
||||
_entry = entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first sector of the partion (relative to start of disk) as a Logical Block Address.
|
||||
/// </summary>
|
||||
public override long FirstSector
|
||||
{
|
||||
get { return _entry.FirstUsedLogicalBlock; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last sector of the partion (relative to start of disk) as a Logical Block Address (inclusive).
|
||||
/// </summary>
|
||||
public override long LastSector
|
||||
{
|
||||
get { return _entry.LastUsedLogicalBlock; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the partition, as a GUID.
|
||||
/// </summary>
|
||||
public override Guid GuidType
|
||||
{
|
||||
get { return _entry.PartitionType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Always returns Zero.
|
||||
/// </summary>
|
||||
public override byte BiosType
|
||||
{
|
||||
get { return 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the partition as a string.
|
||||
/// </summary>
|
||||
public override string TypeAsString
|
||||
{
|
||||
get { return _entry.FriendlyPartitionType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the partition.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return _entry.Name; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the attributes of the partition.
|
||||
/// </summary>
|
||||
public long Attributes
|
||||
{
|
||||
get { return (long)_entry.Attributes; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identity of this specific partition.
|
||||
/// </summary>
|
||||
public Guid Identity
|
||||
{
|
||||
get { return _entry.Identity; }
|
||||
}
|
||||
|
||||
internal override PhysicalVolumeType VolumeType
|
||||
{
|
||||
get { return PhysicalVolumeType.GptPartition; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a stream to access the content of the partition.
|
||||
/// </summary>
|
||||
/// <returns>The new stream.</returns>
|
||||
public override SparseStream Open()
|
||||
{
|
||||
return _table.Open(_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,651 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a GUID Partition Table.
|
||||
/// </summary>
|
||||
public sealed class GuidPartitionTable : PartitionTable
|
||||
{
|
||||
private Stream _diskData;
|
||||
private Geometry _diskGeometry;
|
||||
private GptHeader _primaryHeader;
|
||||
private GptHeader _secondaryHeader;
|
||||
private byte[] _entryBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the GuidPartitionTable class.
|
||||
/// </summary>
|
||||
/// <param name="disk">The disk containing the partition table.</param>
|
||||
public GuidPartitionTable(VirtualDisk disk)
|
||||
{
|
||||
Init(disk.Content, disk.Geometry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the GuidPartitionTable class.
|
||||
/// </summary>
|
||||
/// <param name="disk">The stream containing the disk data.</param>
|
||||
/// <param name="diskGeometry">The geometry of the disk.</param>
|
||||
public GuidPartitionTable(Stream disk, Geometry diskGeometry)
|
||||
{
|
||||
Init(disk, diskGeometry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first sector of the disk available to hold partitions.
|
||||
/// </summary>
|
||||
public long FirstUsableSector
|
||||
{
|
||||
get { return _primaryHeader.FirstUsable; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last sector of the disk available to hold partitions.
|
||||
/// </summary>
|
||||
public long LastUsableSector
|
||||
{
|
||||
get { return _primaryHeader.LastUsable; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique GPT identifier for this disk.
|
||||
/// </summary>
|
||||
public override Guid DiskGuid
|
||||
{
|
||||
get { return _primaryHeader.DiskGuid; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the partitions for storing Operating System file-systems.
|
||||
/// </summary>
|
||||
public override ReadOnlyCollection<PartitionInfo> Partitions
|
||||
{
|
||||
get
|
||||
{
|
||||
return new ReadOnlyCollection<PartitionInfo>(Utilities.Map<GptEntry, GuidPartitionInfo>(GetAllEntries(), (e) => new GuidPartitionInfo(this, e)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partition table on a disk.
|
||||
/// </summary>
|
||||
/// <param name="disk">The disk to initialize.</param>
|
||||
/// <returns>An object to access the newly created partition table.</returns>
|
||||
public static GuidPartitionTable Initialize(VirtualDisk disk)
|
||||
{
|
||||
return Initialize(disk.Content, disk.Geometry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partition table on a disk.
|
||||
/// </summary>
|
||||
/// <param name="disk">The stream containing the disk data.</param>
|
||||
/// <param name="diskGeometry">The geometry of the disk.</param>
|
||||
/// <returns>An object to access the newly created partition table.</returns>
|
||||
public static GuidPartitionTable Initialize(Stream disk, Geometry diskGeometry)
|
||||
{
|
||||
// Create the protective MBR partition record.
|
||||
BiosPartitionTable pt = BiosPartitionTable.Initialize(disk, diskGeometry);
|
||||
pt.CreatePrimaryByCylinder(0, diskGeometry.Cylinders - 1, BiosPartitionTypes.GptProtective, false);
|
||||
|
||||
// Create the GPT headers, and blank-out the entry areas
|
||||
const int EntryCount = 128;
|
||||
const int EntrySize = 128;
|
||||
|
||||
int entrySectors = ((EntryCount * EntrySize) + diskGeometry.BytesPerSector - 1) / diskGeometry.BytesPerSector;
|
||||
|
||||
byte[] entriesBuffer = new byte[EntryCount * EntrySize];
|
||||
|
||||
// Prepare primary header
|
||||
GptHeader header = new GptHeader(diskGeometry.BytesPerSector);
|
||||
header.HeaderLba = 1;
|
||||
header.AlternateHeaderLba = (disk.Length / diskGeometry.BytesPerSector) - 1;
|
||||
header.FirstUsable = header.HeaderLba + entrySectors + 1;
|
||||
header.LastUsable = header.AlternateHeaderLba - entrySectors - 1;
|
||||
header.DiskGuid = Guid.NewGuid();
|
||||
header.PartitionEntriesLba = 2;
|
||||
header.PartitionEntryCount = EntryCount;
|
||||
header.PartitionEntrySize = EntrySize;
|
||||
header.EntriesCrc = CalcEntriesCrc(entriesBuffer);
|
||||
|
||||
// Write the primary header
|
||||
byte[] headerBuffer = new byte[diskGeometry.BytesPerSector];
|
||||
header.WriteTo(headerBuffer, 0);
|
||||
disk.Position = header.HeaderLba * diskGeometry.BytesPerSector;
|
||||
disk.Write(headerBuffer, 0, headerBuffer.Length);
|
||||
|
||||
// Calc alternate header
|
||||
header.HeaderLba = header.AlternateHeaderLba;
|
||||
header.AlternateHeaderLba = 1;
|
||||
header.PartitionEntriesLba = header.HeaderLba - entrySectors;
|
||||
|
||||
// Write the alternate header
|
||||
header.WriteTo(headerBuffer, 0);
|
||||
disk.Position = header.HeaderLba * diskGeometry.BytesPerSector;
|
||||
disk.Write(headerBuffer, 0, headerBuffer.Length);
|
||||
|
||||
return new GuidPartitionTable(disk, diskGeometry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partition table on a disk containing a single partition.
|
||||
/// </summary>
|
||||
/// <param name="disk">The disk to initialize.</param>
|
||||
/// <param name="type">The partition type for the single partition.</param>
|
||||
/// <returns>An object to access the newly created partition table.</returns>
|
||||
public static GuidPartitionTable Initialize(VirtualDisk disk, WellKnownPartitionType type)
|
||||
{
|
||||
GuidPartitionTable pt = Initialize(disk);
|
||||
pt.Create(type, true);
|
||||
return pt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partition that encompasses the entire disk.
|
||||
/// </summary>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <returns>The index of the partition.</returns>
|
||||
/// <remarks>The partition table must be empty before this method is called,
|
||||
/// otherwise IOException is thrown.</remarks>
|
||||
public override int Create(WellKnownPartitionType type, bool active)
|
||||
{
|
||||
List<GptEntry> allEntries = new List<GptEntry>(GetAllEntries());
|
||||
|
||||
EstablishReservedPartition(allEntries);
|
||||
|
||||
// Fill the rest of the disk with the requested partition
|
||||
long start = FirstAvailableSector(allEntries);
|
||||
long end = FindLastFreeSector(start, allEntries);
|
||||
|
||||
return Create(start, end, GuidPartitionTypes.Convert(type), 0, "Data Partition");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new primary partition with a target size.
|
||||
/// </summary>
|
||||
/// <param name="size">The target size (in bytes).</param>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <returns>The index of the new partition.</returns>
|
||||
public override int Create(long size, WellKnownPartitionType type, bool active)
|
||||
{
|
||||
if (size < _diskGeometry.BytesPerSector)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("size", size, "size must be at least one sector");
|
||||
}
|
||||
|
||||
long sectorLength = size / _diskGeometry.BytesPerSector;
|
||||
long start = FindGap(size / _diskGeometry.BytesPerSector, 1);
|
||||
|
||||
return Create(start, start + sectorLength - 1, GuidPartitionTypes.Convert(type), 0, "Data Partition");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new aligned partition that encompasses the entire disk.
|
||||
/// </summary>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <param name="alignment">The alignment (in bytes).</param>
|
||||
/// <returns>The index of the partition.</returns>
|
||||
/// <remarks>The partition table must be empty before this method is called,
|
||||
/// otherwise IOException is thrown.</remarks>
|
||||
/// <remarks>
|
||||
/// Traditionally partitions were aligned to the physical structure of the underlying disk,
|
||||
/// however with modern storage greater efficiency is acheived by aligning partitions on
|
||||
/// large values that are a power of two.
|
||||
/// </remarks>
|
||||
public override int CreateAligned(WellKnownPartitionType type, bool active, int alignment)
|
||||
{
|
||||
if (alignment % _diskGeometry.BytesPerSector != 0)
|
||||
{
|
||||
throw new ArgumentException("Alignment is not a multiple of the sector size");
|
||||
}
|
||||
|
||||
List<GptEntry> allEntries = new List<GptEntry>(GetAllEntries());
|
||||
|
||||
EstablishReservedPartition(allEntries);
|
||||
|
||||
// Fill the rest of the disk with the requested partition
|
||||
long start = Utilities.RoundUp(FirstAvailableSector(allEntries), alignment / _diskGeometry.BytesPerSector);
|
||||
long end = Utilities.RoundDown(FindLastFreeSector(start, allEntries) + 1, alignment / _diskGeometry.BytesPerSector);
|
||||
|
||||
if (end <= start)
|
||||
{
|
||||
throw new IOException("No available space");
|
||||
}
|
||||
|
||||
return Create(start, end - 1, GuidPartitionTypes.Convert(type), 0, "Data Partition");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new aligned partition with a target size.
|
||||
/// </summary>
|
||||
/// <param name="size">The target size (in bytes).</param>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <param name="alignment">The alignment (in bytes).</param>
|
||||
/// <returns>The index of the new partition.</returns>
|
||||
/// <remarks>
|
||||
/// Traditionally partitions were aligned to the physical structure of the underlying disk,
|
||||
/// however with modern storage greater efficiency is achieved by aligning partitions on
|
||||
/// large values that are a power of two.
|
||||
/// </remarks>
|
||||
public override int CreateAligned(long size, WellKnownPartitionType type, bool active, int alignment)
|
||||
{
|
||||
if (size < _diskGeometry.BytesPerSector)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("size", size, "size must be at least one sector");
|
||||
}
|
||||
|
||||
if (alignment % _diskGeometry.BytesPerSector != 0)
|
||||
{
|
||||
throw new ArgumentException("Alignment is not a multiple of the sector size");
|
||||
}
|
||||
|
||||
if (size % alignment != 0)
|
||||
{
|
||||
throw new ArgumentException("Size is not a multiple of the alignment");
|
||||
}
|
||||
|
||||
long sectorLength = size / _diskGeometry.BytesPerSector;
|
||||
long start = FindGap(size / _diskGeometry.BytesPerSector, alignment / _diskGeometry.BytesPerSector);
|
||||
|
||||
return Create(start, start + sectorLength - 1, GuidPartitionTypes.Convert(type), 0, "Data Partition");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GUID partition on the disk.
|
||||
/// </summary>
|
||||
/// <param name="startSector">The first sector of the partition.</param>
|
||||
/// <param name="endSector">The last sector of the partition.</param>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="attributes">The partition attributes.</param>
|
||||
/// <param name="name">The name of the partition.</param>
|
||||
/// <returns>The index of the new partition.</returns>
|
||||
/// <remarks>No checking is performed on the parameters, the caller is
|
||||
/// responsible for ensuring that the partition does not overlap other partitions.</remarks>
|
||||
public int Create(long startSector, long endSector, Guid type, long attributes, string name)
|
||||
{
|
||||
GptEntry newEntry = CreateEntry(startSector, endSector, type, attributes, name);
|
||||
return GetEntryIndex(newEntry.Identity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a partition at a given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the partition.</param>
|
||||
public override void Delete(int index)
|
||||
{
|
||||
int offset = GetPartitionOffset(index);
|
||||
Array.Clear(_entryBuffer, offset, _primaryHeader.PartitionEntrySize);
|
||||
Write();
|
||||
}
|
||||
|
||||
internal SparseStream Open(GptEntry entry)
|
||||
{
|
||||
long start = entry.FirstUsedLogicalBlock * _diskGeometry.BytesPerSector;
|
||||
long end = (entry.LastUsedLogicalBlock + 1) * _diskGeometry.BytesPerSector;
|
||||
return new SubStream(_diskData, start, end - start);
|
||||
}
|
||||
|
||||
private static uint CalcEntriesCrc(byte[] buffer)
|
||||
{
|
||||
return Crc32LittleEndian.Compute(Crc32Algorithm.Common, buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
private static int CountEntries<T>(ICollection<T> values, Func<T, bool> pred)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (var val in values)
|
||||
{
|
||||
if (pred(val))
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private void Init(Stream disk, Geometry diskGeometry)
|
||||
{
|
||||
BiosPartitionTable bpt;
|
||||
try
|
||||
{
|
||||
bpt = new BiosPartitionTable(disk, diskGeometry);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new IOException("Invalid GPT disk, protective MBR table not present or invalid", ioe);
|
||||
}
|
||||
|
||||
if (bpt.Count != 1 || bpt[0].BiosType != BiosPartitionTypes.GptProtective)
|
||||
{
|
||||
throw new IOException("Invalid GPT disk, protective MBR table is not valid");
|
||||
}
|
||||
|
||||
_diskData = disk;
|
||||
_diskGeometry = diskGeometry;
|
||||
|
||||
disk.Position = diskGeometry.BytesPerSector;
|
||||
byte[] sector = Utilities.ReadFully(disk, diskGeometry.BytesPerSector);
|
||||
|
||||
_primaryHeader = new GptHeader(diskGeometry.BytesPerSector);
|
||||
if (!_primaryHeader.ReadFrom(sector, 0) || !ReadEntries(_primaryHeader))
|
||||
{
|
||||
disk.Position = disk.Length - diskGeometry.BytesPerSector;
|
||||
disk.Read(sector, 0, sector.Length);
|
||||
_secondaryHeader = new GptHeader(diskGeometry.BytesPerSector);
|
||||
if (!_secondaryHeader.ReadFrom(sector, 0) || !ReadEntries(_secondaryHeader))
|
||||
{
|
||||
throw new IOException("No valid GUID Partition Table found");
|
||||
}
|
||||
|
||||
// Generate from the primary table from the secondary one
|
||||
_primaryHeader = new GptHeader(_secondaryHeader);
|
||||
_primaryHeader.HeaderLba = _secondaryHeader.AlternateHeaderLba;
|
||||
_primaryHeader.AlternateHeaderLba = _secondaryHeader.HeaderLba;
|
||||
_primaryHeader.PartitionEntriesLba = 2;
|
||||
|
||||
// If the disk is writeable, fix up the primary partition table based on the
|
||||
// (valid) secondary table.
|
||||
if (disk.CanWrite)
|
||||
{
|
||||
WritePrimaryHeader();
|
||||
}
|
||||
}
|
||||
|
||||
if (_secondaryHeader == null)
|
||||
{
|
||||
_secondaryHeader = new GptHeader(diskGeometry.BytesPerSector);
|
||||
disk.Position = disk.Length - diskGeometry.BytesPerSector;
|
||||
disk.Read(sector, 0, sector.Length);
|
||||
if (!_secondaryHeader.ReadFrom(sector, 0) || !ReadEntries(_secondaryHeader))
|
||||
{
|
||||
// Generate from the secondary table from the primary one
|
||||
_secondaryHeader = new GptHeader(_primaryHeader);
|
||||
_secondaryHeader.HeaderLba = _secondaryHeader.AlternateHeaderLba;
|
||||
_secondaryHeader.AlternateHeaderLba = _secondaryHeader.HeaderLba;
|
||||
_secondaryHeader.PartitionEntriesLba = _secondaryHeader.HeaderLba - Utilities.RoundUp(_secondaryHeader.PartitionEntryCount * _secondaryHeader.PartitionEntrySize, diskGeometry.BytesPerSector);
|
||||
|
||||
// If the disk is writeable, fix up the secondary partition table based on the
|
||||
// (valid) primary table.
|
||||
if (disk.CanWrite)
|
||||
{
|
||||
WriteSecondaryHeader();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EstablishReservedPartition(List<GptEntry> allEntries)
|
||||
{
|
||||
// If no MicrosoftReserved partition, and no Microsoft Data partitions, and the disk
|
||||
// has a 'reasonable' size free, create a Microsoft Reserved partition.
|
||||
if (CountEntries(allEntries, e => e.PartitionType == GuidPartitionTypes.MicrosoftReserved) == 0
|
||||
&& CountEntries(allEntries, e => e.PartitionType == GuidPartitionTypes.WindowsBasicData) == 0
|
||||
&& _diskGeometry.Capacity > 512 * 1024 * 1024)
|
||||
{
|
||||
long reservedStart = FirstAvailableSector(allEntries);
|
||||
long reservedEnd = FindLastFreeSector(reservedStart, allEntries);
|
||||
|
||||
if ((reservedEnd - reservedStart + 1) * _diskGeometry.BytesPerSector > 512 * 1024 * 1024)
|
||||
{
|
||||
long size = ((_diskGeometry.Capacity < (16 * 1024L * 1024 * 1024)) ? 32 : 128) * 1024 * 1024;
|
||||
reservedEnd = reservedStart + (size / _diskGeometry.BytesPerSector) - 1;
|
||||
|
||||
int reservedOffset = GetFreeEntryOffset();
|
||||
GptEntry newReservedEntry = new GptEntry();
|
||||
newReservedEntry.PartitionType = GuidPartitionTypes.MicrosoftReserved;
|
||||
newReservedEntry.Identity = Guid.NewGuid();
|
||||
newReservedEntry.FirstUsedLogicalBlock = reservedStart;
|
||||
newReservedEntry.LastUsedLogicalBlock = reservedEnd;
|
||||
newReservedEntry.Attributes = 0;
|
||||
newReservedEntry.Name = "Microsoft reserved partition";
|
||||
newReservedEntry.WriteTo(_entryBuffer, reservedOffset);
|
||||
allEntries.Add(newReservedEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GptEntry CreateEntry(long startSector, long endSector, Guid type, long attributes, string name)
|
||||
{
|
||||
if (endSector < startSector)
|
||||
{
|
||||
throw new ArgumentException("The end sector is before the start sector");
|
||||
}
|
||||
|
||||
int offset = GetFreeEntryOffset();
|
||||
GptEntry newEntry = new GptEntry();
|
||||
newEntry.PartitionType = type;
|
||||
newEntry.Identity = Guid.NewGuid();
|
||||
newEntry.FirstUsedLogicalBlock = startSector;
|
||||
newEntry.LastUsedLogicalBlock = endSector;
|
||||
newEntry.Attributes = (ulong)attributes;
|
||||
newEntry.Name = name;
|
||||
newEntry.WriteTo(_entryBuffer, offset);
|
||||
|
||||
// Commit changes to disk
|
||||
Write();
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
private long FindGap(long numSectors, long alignmentSectors)
|
||||
{
|
||||
List<GptEntry> list = new List<GptEntry>(GetAllEntries());
|
||||
list.Sort();
|
||||
|
||||
long startSector = Utilities.RoundUp(_primaryHeader.FirstUsable, alignmentSectors);
|
||||
foreach (var entry in list)
|
||||
{
|
||||
if (!Utilities.RangesOverlap(startSector, startSector + numSectors - 1, entry.FirstUsedLogicalBlock, entry.LastUsedLogicalBlock))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
startSector = Utilities.RoundUp(entry.LastUsedLogicalBlock + 1, alignmentSectors);
|
||||
}
|
||||
}
|
||||
|
||||
if (_diskGeometry.TotalSectorsLong - startSector < numSectors)
|
||||
{
|
||||
throw new IOException(string.Format(CultureInfo.InvariantCulture, "Unable to find free space of {0} sectors", numSectors));
|
||||
}
|
||||
|
||||
return startSector;
|
||||
}
|
||||
|
||||
private long FirstAvailableSector(List<GptEntry> allEntries)
|
||||
{
|
||||
long start = _primaryHeader.FirstUsable;
|
||||
|
||||
foreach (GptEntry entry in allEntries)
|
||||
{
|
||||
if (entry.LastUsedLogicalBlock >= start)
|
||||
{
|
||||
start = entry.LastUsedLogicalBlock + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
private long FindLastFreeSector(long start, List<GptEntry> allEntries)
|
||||
{
|
||||
long end = _primaryHeader.LastUsable;
|
||||
|
||||
foreach (GptEntry entry in allEntries)
|
||||
{
|
||||
if (entry.LastUsedLogicalBlock > start && entry.FirstUsedLogicalBlock <= end)
|
||||
{
|
||||
end = entry.FirstUsedLogicalBlock - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
private void Write()
|
||||
{
|
||||
WritePrimaryHeader();
|
||||
WriteSecondaryHeader();
|
||||
}
|
||||
|
||||
private void WritePrimaryHeader()
|
||||
{
|
||||
byte[] buffer = new byte[_diskGeometry.BytesPerSector];
|
||||
_primaryHeader.EntriesCrc = CalcEntriesCrc();
|
||||
_primaryHeader.WriteTo(buffer, 0);
|
||||
_diskData.Position = _diskGeometry.BytesPerSector;
|
||||
_diskData.Write(buffer, 0, buffer.Length);
|
||||
|
||||
_diskData.Position = 2 * _diskGeometry.BytesPerSector;
|
||||
_diskData.Write(_entryBuffer, 0, _entryBuffer.Length);
|
||||
}
|
||||
|
||||
private void WriteSecondaryHeader()
|
||||
{
|
||||
byte[] buffer = new byte[_diskGeometry.BytesPerSector];
|
||||
_secondaryHeader.EntriesCrc = CalcEntriesCrc();
|
||||
_secondaryHeader.WriteTo(buffer, 0);
|
||||
_diskData.Position = _diskData.Length - _diskGeometry.BytesPerSector;
|
||||
_diskData.Write(buffer, 0, buffer.Length);
|
||||
|
||||
_diskData.Position = _secondaryHeader.PartitionEntriesLba * _diskGeometry.BytesPerSector;
|
||||
_diskData.Write(_entryBuffer, 0, _entryBuffer.Length);
|
||||
}
|
||||
|
||||
private bool ReadEntries(GptHeader header)
|
||||
{
|
||||
_diskData.Position = header.PartitionEntriesLba * _diskGeometry.BytesPerSector;
|
||||
_entryBuffer = Utilities.ReadFully(_diskData, (int)(header.PartitionEntrySize * header.PartitionEntryCount));
|
||||
if (header.EntriesCrc != CalcEntriesCrc())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private uint CalcEntriesCrc()
|
||||
{
|
||||
return Crc32LittleEndian.Compute(Crc32Algorithm.Common, _entryBuffer, 0, _entryBuffer.Length);
|
||||
}
|
||||
|
||||
private IEnumerable<GptEntry> GetAllEntries()
|
||||
{
|
||||
for (int i = 0; i < _primaryHeader.PartitionEntryCount; ++i)
|
||||
{
|
||||
GptEntry entry = new GptEntry();
|
||||
entry.ReadFrom(_entryBuffer, i * _primaryHeader.PartitionEntrySize);
|
||||
if (entry.PartitionType != Guid.Empty)
|
||||
{
|
||||
yield return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int GetPartitionOffset(int index)
|
||||
{
|
||||
bool found = false;
|
||||
int entriesSoFar = 0;
|
||||
int position = 0;
|
||||
|
||||
while (!found && position < _primaryHeader.PartitionEntryCount)
|
||||
{
|
||||
GptEntry entry = new GptEntry();
|
||||
entry.ReadFrom(_entryBuffer, position * _primaryHeader.PartitionEntrySize);
|
||||
if (entry.PartitionType != Guid.Empty)
|
||||
{
|
||||
if (index == entriesSoFar)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
entriesSoFar++;
|
||||
}
|
||||
|
||||
position++;
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
return position * _primaryHeader.PartitionEntrySize;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException(string.Format(CultureInfo.InvariantCulture, "No such partition: {0}", index));
|
||||
}
|
||||
}
|
||||
|
||||
private int GetEntryIndex(Guid identity)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < _primaryHeader.PartitionEntryCount; ++i)
|
||||
{
|
||||
GptEntry entry = new GptEntry();
|
||||
entry.ReadFrom(_entryBuffer, i * _primaryHeader.PartitionEntrySize);
|
||||
|
||||
if (entry.Identity == identity)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
else if (entry.PartitionType != Guid.Empty)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("No such partition");
|
||||
}
|
||||
|
||||
private int GetFreeEntryOffset()
|
||||
{
|
||||
for (int i = 0; i < _primaryHeader.PartitionEntryCount; ++i)
|
||||
{
|
||||
GptEntry entry = new GptEntry();
|
||||
entry.ReadFrom(_entryBuffer, i * _primaryHeader.PartitionEntrySize);
|
||||
|
||||
if (entry.PartitionType == Guid.Empty)
|
||||
{
|
||||
return i * _primaryHeader.PartitionEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("No free partition entries available");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Convenient access to well known GPT partition types.
|
||||
/// </summary>
|
||||
public static class GuidPartitionTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// EFI system partition.
|
||||
/// </summary>
|
||||
public static readonly Guid EfiSystem = new Guid("C12A7328-F81F-11D2-BA4B-00A0C93EC93B");
|
||||
|
||||
/// <summary>
|
||||
/// BIOS boot partition.
|
||||
/// </summary>
|
||||
public static readonly Guid BiosBoot = new Guid("21686148-6449-6E6F-744E-656564454649");
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft reserved partition.
|
||||
/// </summary>
|
||||
public static readonly Guid MicrosoftReserved = new Guid("E3C9E316-0B5C-4DB8-817D-F92DF00215AE");
|
||||
|
||||
/// <summary>
|
||||
/// Windows basic data partition.
|
||||
/// </summary>
|
||||
public static readonly Guid WindowsBasicData = new Guid("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7");
|
||||
|
||||
/// <summary>
|
||||
/// Linux LVM partition.
|
||||
/// </summary>
|
||||
public static readonly Guid LinuxLvm = new Guid("E6D6D379-F507-44C2-A23C-238F2A3DF928");
|
||||
|
||||
/// <summary>
|
||||
/// Linux swap partition.
|
||||
/// </summary>
|
||||
public static readonly Guid LinuxSwap = new Guid("0657FD6D-A4AB-43C4-84E5-0933C84B4F4F");
|
||||
|
||||
/// <summary>
|
||||
/// Windows Logical Disk Manager metadata.
|
||||
/// </summary>
|
||||
public static readonly Guid WindowsLdmMetadata = new Guid("5808C8AA-7E8F-42E0-85D2-E1E90434CFB3");
|
||||
|
||||
/// <summary>
|
||||
/// Windows Logical Disk Manager data.
|
||||
/// </summary>
|
||||
public static readonly Guid WindowsLdmData = new Guid("AF9B60A0-1431-4F62-BC68-3311714A69AD");
|
||||
|
||||
/// <summary>
|
||||
/// Converts a well known partition type to a Guid.
|
||||
/// </summary>
|
||||
/// <param name="wellKnown">The value to convert.</param>
|
||||
/// <returns>The GUID value.</returns>
|
||||
internal static Guid Convert(WellKnownPartitionType wellKnown)
|
||||
{
|
||||
switch (wellKnown)
|
||||
{
|
||||
case WellKnownPartitionType.Linux:
|
||||
case WellKnownPartitionType.WindowsFat:
|
||||
case WellKnownPartitionType.WindowsNtfs:
|
||||
return WindowsBasicData;
|
||||
case WellKnownPartitionType.LinuxLvm:
|
||||
return LinuxLvm;
|
||||
case WellKnownPartitionType.LinuxSwap:
|
||||
return LinuxSwap;
|
||||
default:
|
||||
throw new ArgumentException("Unknown partition type");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Base class representing a disk partition.
|
||||
/// </summary>
|
||||
/// <remarks>The purpose of this class is to provide a minimal view of a partition,
|
||||
/// such that callers can access existing partitions without specific knowledge of
|
||||
/// the partitioning system.</remarks>
|
||||
public abstract class PartitionInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the first sector of the partion (relative to start of disk) as a Logical Block Address.
|
||||
/// </summary>
|
||||
public abstract long FirstSector { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last sector of the partion (relative to start of disk) as a Logical Block Address (inclusive).
|
||||
/// </summary>
|
||||
public abstract long LastSector { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the partition in sectors.
|
||||
/// </summary>
|
||||
public virtual long SectorCount
|
||||
{
|
||||
get { return 1 + LastSector - FirstSector; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the partition, as a GUID, when available.
|
||||
/// </summary>
|
||||
/// <remarks><see cref="System.Guid"/>.Empty for MBR-style partitions.</remarks>
|
||||
public abstract Guid GuidType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the partition, in legacy BIOS form, when available.
|
||||
/// </summary>
|
||||
/// <remarks>Zero for GUID-style partitions.</remarks>
|
||||
public abstract byte BiosType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the partition type as a 'friendly' string.
|
||||
/// </summary>
|
||||
public abstract string TypeAsString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the physical volume type for this type of partition.
|
||||
/// </summary>
|
||||
internal abstract PhysicalVolumeType VolumeType
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a stream that accesses the partition's contents.
|
||||
/// </summary>
|
||||
/// <returns>The new stream.</returns>
|
||||
public abstract SparseStream Open();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a summary of the partition information as 'first - last (type)'.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the partition information.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, "0x{0:X} - 0x{1:X} ({2})", FirstSector, LastSector, TypeAsString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for classes which represent a disk partitioning scheme.
|
||||
/// </summary>
|
||||
/// <remarks>After modifying the table, by creating or deleting a partition assume that any
|
||||
/// previously stored partition indexes of higher value are no longer valid. Re-enumerate
|
||||
/// the partitions to discover the next index-to-partition mapping.</remarks>
|
||||
public abstract class PartitionTable
|
||||
{
|
||||
private static List<PartitionTableFactory> s_factories;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GUID that uniquely identifies this disk, if supported (else returns <c>null</c>).
|
||||
/// </summary>
|
||||
public abstract Guid DiskGuid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of partitions that contain user data (i.e. non-system / empty).
|
||||
/// </summary>
|
||||
public abstract ReadOnlyCollection<PartitionInfo> Partitions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of User partitions on the disk.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get { return Partitions.Count; }
|
||||
}
|
||||
|
||||
private static List<PartitionTableFactory> Factories
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_factories == null)
|
||||
{
|
||||
List<PartitionTableFactory> factories = new List<PartitionTableFactory>();
|
||||
|
||||
foreach (var type in typeof(VolumeManager).Assembly.GetTypes())
|
||||
{
|
||||
foreach (PartitionTableFactoryAttribute attr in Attribute.GetCustomAttributes(type, typeof(PartitionTableFactoryAttribute), false))
|
||||
{
|
||||
factories.Add((PartitionTableFactory)Activator.CreateInstance(type));
|
||||
}
|
||||
}
|
||||
|
||||
s_factories = factories;
|
||||
}
|
||||
|
||||
return s_factories;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about a particular User partition.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the partition.</param>
|
||||
/// <returns>Information about the partition.</returns>
|
||||
public PartitionInfo this[int index]
|
||||
{
|
||||
get { return Partitions[index]; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a disk is partitioned with a known partitioning scheme.
|
||||
/// </summary>
|
||||
/// <param name="content">The content of the disk to check.</param>
|
||||
/// <returns><c>true</c> if the disk is partitioned, else <c>false</c>.</returns>
|
||||
public static bool IsPartitioned(Stream content)
|
||||
{
|
||||
foreach (var partTableFactory in Factories)
|
||||
{
|
||||
if (partTableFactory.DetectIsPartitioned(content))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a disk is partitioned with a known partitioning scheme.
|
||||
/// </summary>
|
||||
/// <param name="disk">The disk to check.</param>
|
||||
/// <returns><c>true</c> if the disk is partitioned, else <c>false</c>.</returns>
|
||||
public static bool IsPartitioned(VirtualDisk disk)
|
||||
{
|
||||
return IsPartitioned(disk.Content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all of the partition tables found on a disk.
|
||||
/// </summary>
|
||||
/// <param name="disk">The disk to inspect.</param>
|
||||
/// <returns>It is rare for a disk to have multiple partition tables, but theoretically
|
||||
/// possible.</returns>
|
||||
public static IList<PartitionTable> GetPartitionTables(VirtualDisk disk)
|
||||
{
|
||||
List<PartitionTable> tables = new List<PartitionTable>();
|
||||
|
||||
foreach (var factory in Factories)
|
||||
{
|
||||
PartitionTable table = factory.DetectPartitionTable(disk);
|
||||
if (table != null)
|
||||
{
|
||||
tables.Add(table);
|
||||
}
|
||||
}
|
||||
|
||||
return tables;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all of the partition tables found on a disk.
|
||||
/// </summary>
|
||||
/// <param name="contentStream">The content of the disk to inspect.</param>
|
||||
/// <returns>It is rare for a disk to have multiple partition tables, but theoretically
|
||||
/// possible.</returns>
|
||||
public static IList<PartitionTable> GetPartitionTables(Stream contentStream)
|
||||
{
|
||||
return GetPartitionTables(new Raw.Disk(contentStream, Ownership.None));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partition that encompasses the entire disk.
|
||||
/// </summary>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <returns>The index of the partition.</returns>
|
||||
/// <remarks>The partition table must be empty before this method is called,
|
||||
/// otherwise IOException is thrown.</remarks>
|
||||
public abstract int Create(WellKnownPartitionType type, bool active);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partition with a target size.
|
||||
/// </summary>
|
||||
/// <param name="size">The target size (in bytes).</param>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <returns>The index of the new partition.</returns>
|
||||
public abstract int Create(long size, WellKnownPartitionType type, bool active);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new aligned partition that encompasses the entire disk.
|
||||
/// </summary>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <param name="alignment">The alignment (in byte).</param>
|
||||
/// <returns>The index of the partition.</returns>
|
||||
/// <remarks>The partition table must be empty before this method is called,
|
||||
/// otherwise IOException is thrown.</remarks>
|
||||
/// <remarks>
|
||||
/// Traditionally partitions were aligned to the physical structure of the underlying disk,
|
||||
/// however with modern storage greater efficiency is acheived by aligning partitions on
|
||||
/// large values that are a power of two.
|
||||
/// </remarks>
|
||||
public abstract int CreateAligned(WellKnownPartitionType type, bool active, int alignment);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new aligned partition with a target size.
|
||||
/// </summary>
|
||||
/// <param name="size">The target size (in bytes).</param>
|
||||
/// <param name="type">The partition type.</param>
|
||||
/// <param name="active">Whether the partition is active (bootable).</param>
|
||||
/// <param name="alignment">The alignment (in byte).</param>
|
||||
/// <returns>The index of the new partition.</returns>
|
||||
/// <remarks>
|
||||
/// Traditionally partitions were aligned to the physical structure of the underlying disk,
|
||||
/// however with modern storage greater efficiency is achieved by aligning partitions on
|
||||
/// large values that are a power of two.
|
||||
/// </remarks>
|
||||
public abstract int CreateAligned(long size, WellKnownPartitionType type, bool active, int alignment);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a partition at a given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the partition.</param>
|
||||
public abstract void Delete(int index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
internal abstract class PartitionTableFactory
|
||||
{
|
||||
public abstract bool DetectIsPartitioned(Stream s);
|
||||
|
||||
public abstract PartitionTable DetectPartitionTable(VirtualDisk disk);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
using System;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
internal sealed class PartitionTableFactoryAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
namespace DiscUtils.Partitions
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of partition-table technology neutral partition types.
|
||||
/// </summary>
|
||||
public enum WellKnownPartitionType
|
||||
{
|
||||
/// <summary>
|
||||
/// Windows FAT-based partition.
|
||||
/// </summary>
|
||||
WindowsFat = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Windows NTFS-based partition.
|
||||
/// </summary>
|
||||
WindowsNtfs = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Linux native file system.
|
||||
/// </summary>
|
||||
Linux = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Linux swap.
|
||||
/// </summary>
|
||||
LinuxSwap = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Linux Logical Volume Manager (LVM).
|
||||
/// </summary>
|
||||
LinuxLvm = 4,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user