Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
chsbuffer committed Sep 1, 2024
0 parents commit 81bbedf
Show file tree
Hide file tree
Showing 32 changed files with 2,718 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
bin
obj
publish
.vs
.idea
.vscode
*.user
13 changes: 13 additions & 0 deletions DiskPartitionInfo/DiskPartitionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using DiskPartitionInfo.FluentApi;

namespace DiskPartitionInfo
{
public static class DiskPartitionInfo
{
public static IMbrReader ReadMbr()
=> new MbrReader();

public static IGptReaderLocation ReadGpt()
=> new GptReader();
}
}
25 changes: 25 additions & 0 deletions DiskPartitionInfo/Extensions/ByteArrayExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Runtime.InteropServices;

namespace DiskPartitionInfo.Extensions
{
internal static class ByteArrayExtensions
{
internal static T ToStruct<T>(this byte[] bytes)
where T : struct
{
T result;
var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);

try
{
result = Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject())!;
}
finally
{
handle.Free();
}

return result;
}
}
}
65 changes: 65 additions & 0 deletions DiskPartitionInfo/FluentApi/GptReader.Windows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// TODO: Deduplicate this and the MbrReader.Windows.cs
#pragma warning disable CA1416 // This call site is reachable on all platforms.
#if Windows
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using DiskPartitionInfo.Gpt;

namespace DiskPartitionInfo.FluentApi
{
internal partial class GptReader
{
/// <inheritdoc/>
public GuidPartitionTable FromPhysicalDriveNumber(int driveNumber)
=> FromPath(@"\\.\PhysicalDrive" + driveNumber);

/// <inheritdoc/>
public GuidPartitionTable FromVolumeLetter(string volumeLetter)
{
if (!volumeLetter.EndsWith(':'))
volumeLetter += ':';

var partition = GetPartitions(volumeLetter).FirstOrDefault();

if (partition is null)
throw new ArgumentException(
$"Could not find a drive for volume {volumeLetter}", nameof(volumeLetter));

var drive = GetDrives(partition["DeviceID"].ToString()!).FirstOrDefault();

if (drive is null)
throw new ArgumentException(
$"Could not find a drive for volume {volumeLetter}", nameof(volumeLetter));

return FromPath(drive["DeviceID"].ToString()!);
}

private static IEnumerable<ManagementBaseObject> GetPartitions(string volumeLetter)
=> ExecuteWmicQuery($@"
ASSOCIATORS OF
{{Win32_LogicalDisk.DeviceID='{volumeLetter}'}}
WHERE
AssocClass = Win32_LogicalDiskToPartition");

private static IEnumerable<ManagementBaseObject> GetDrives(string partitionId)
=> ExecuteWmicQuery($@"
ASSOCIATORS OF
{{Win32_DiskPartition.DeviceID='{partitionId}'}}
WHERE
AssocClass = Win32_DiskDriveToDiskPartition");

private static IEnumerable<ManagementBaseObject> ExecuteWmicQuery(string query)
{
var queryResults = new ManagementObjectSearcher(query);
var result = queryResults.Get();

return result is null
? Enumerable.Empty<ManagementBaseObject>()
: result.OfType<ManagementBaseObject>();
}
}
}
#endif
#pragma warning restore CA1416 // This call site is reachable on all platforms.
78 changes: 78 additions & 0 deletions DiskPartitionInfo/FluentApi/GptReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.IO;
using DiskPartitionInfo.Extensions;
using DiskPartitionInfo.Gpt;
using GptPartitionStruct = DiskPartitionInfo.Models.GptPartitionEntry;
using GptStruct = DiskPartitionInfo.Models.GuidPartitionTable;

namespace DiskPartitionInfo.FluentApi
{
internal partial class GptReader : IGptReader, IGptReaderLocation
{
private const int SectorSize = 512;

private bool _usePrimary = true;

/// <inheritdoc/>
public IGptReader Primary()
{
_usePrimary = true;
return this;
}

/// <inheritdoc/>
public IGptReader Secondary()
{
_usePrimary = false;
return this;
}

/// <inheritdoc/>
public GuidPartitionTable FromPath(string path)
{
using var stream = File.OpenRead(path);

return FromStream(stream);
}

/// <inheritdoc/>
public GuidPartitionTable FromStream(Stream stream)
{
if (_usePrimary)
stream.Seek(SectorSize, SeekOrigin.Begin);
else
stream.Seek(-1 * SectorSize, SeekOrigin.End);

var gpt = ReadGpt(stream);

stream.Seek((long) gpt.PartitionsArrayLba * SectorSize, SeekOrigin.Begin);

var partitions = ReadPartitions(stream, gpt);

return new GuidPartitionTable(gpt, partitions);
}

private static GptStruct ReadGpt(Stream stream)
{
var gptData = new byte[SectorSize];
stream.Read(buffer: gptData, offset: 0, count: SectorSize);

return gptData.ToStruct<GptStruct>();
}

private static List<GptPartitionStruct> ReadPartitions(Stream stream, GptStruct gpt)
{
var partitions = new List<GptPartitionStruct>();

for (var i = 0; i < gpt.PartitionsCount; ++i)
{
var partition = new byte[gpt.PartitionEntryLength];

stream.Read(buffer: partition, offset: 0, count: (int) gpt.PartitionEntryLength);
partitions.Add(partition.ToStruct<GptPartitionStruct>());
}

return partitions;
}
}
}
42 changes: 42 additions & 0 deletions DiskPartitionInfo/FluentApi/IGptReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.IO;
using DiskPartitionInfo.Gpt;

namespace DiskPartitionInfo.FluentApi
{
public interface IGptReader
{
#if Windows
/// <summary>
/// Reads the GPT from the physical drive given its number.
/// </summary>
/// <param name="driveNumber">Drive number, e.g. 0 or 3.</param>
/// <returns>The GUID Partition Table information.</returns>
GuidPartitionTable FromPhysicalDriveNumber(int driveNumber);

/// <summary>
/// Reads the GPT record from the physical drive given a volume letter.
/// GPT will be read from the corresponding physical drive if it exists.
/// </summary>
/// <param name="volumeLetter">Volume letter, e.g. C: or F:.</param>
/// <returns>The GUID Partition Table information.</returns>
GuidPartitionTable FromVolumeLetter(string volumeLetter);
#endif

/// <summary>
/// Reads the GPT from the given path.
/// It can be a path to a file or to a physical drive.
/// </summary>
/// <param name="path">Path to disk or file to read from,
/// e.g. C:\GPT.bin or ../disk.img.</param>
/// <returns>The GUID Partition Table information.</returns>
GuidPartitionTable FromPath(string path);

/// <summary>
/// Reads the GPT from the given stream.
/// The stream is not automatically closed after read operation.
/// </summary>
/// <param name="stream">Stream to read from.</param>
/// <returns>The GUID Partition Table information.</returns>
GuidPartitionTable FromStream(Stream stream);
}
}
15 changes: 15 additions & 0 deletions DiskPartitionInfo/FluentApi/IGptReaderLocation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace DiskPartitionInfo.FluentApi
{
public interface IGptReaderLocation
{
/// <summary>
/// Read the primary GPT located at the beginning of the disk.
/// </summary>
IGptReader Primary();

/// <summary>
/// Read the secondary GPT located at the end of the disk.
/// </summary>
IGptReader Secondary();
}
}
42 changes: 42 additions & 0 deletions DiskPartitionInfo/FluentApi/IMbrReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.IO;
using DiskPartitionInfo.Mbr;

namespace DiskPartitionInfo.FluentApi
{
public interface IMbrReader
{
#if Windows
/// <summary>
/// Reads the MBR from the physical drive given its number.
/// </summary>
/// <param name="driveNumber">Drive number, e.g. 0 or 3.</param>
/// <returns>The Master Boot Record information.</returns>
MasterBootRecord FromPhysicalDriveNumber(int driveNumber);

/// <summary>
/// Reads the MBR record from the physical drive given a volume letter.
/// MBR will be read from the corresponding physical drive if it exists.
/// </summary>
/// <param name="volumeLetter">Volume letter, e.g. C: or F:.</param>
/// <returns>The Master Boot Record information.</returns>
MasterBootRecord FromVolumeLetter(string volumeLetter);
#endif

/// <summary>
/// Reads the MBR from the given path.
/// It can be a path to a file or to a physical drive.
/// </summary>
/// <param name="path">Path to disk or file to read from,
/// e.g. C:\MBR.bin or ../disk.img.</param>
/// <returns>The Master Boot Record information.</returns>
MasterBootRecord FromPath(string path);

/// <summary>
/// Reads the MBR from the given stream.
/// The stream is not automatically closed after read operation.
/// </summary>
/// <param name="stream">Stream to read from.</param>
/// <returns>The Master Boot Record information.</returns>
MasterBootRecord FromStream(Stream stream);
}
}
64 changes: 64 additions & 0 deletions DiskPartitionInfo/FluentApi/MbrReader.Windows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma warning disable CA1416 // This call site is reachable on all platforms.
#if Windows
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using DiskPartitionInfo.Mbr;

namespace DiskPartitionInfo.FluentApi
{
internal partial class MbrReader : IMbrReader
{
/// <inheritdoc/>
public MasterBootRecord FromPhysicalDriveNumber(int driveNumber)
=> FromPath(@"\\.\PhysicalDrive" + driveNumber);

/// <inheritdoc/>
public MasterBootRecord FromVolumeLetter(string volumeLetter)
{
if (!volumeLetter.EndsWith(':'))
volumeLetter += ':';

var partition = GetPartitions(volumeLetter).FirstOrDefault();

if (partition is null)
throw new ArgumentException(
$"Could not find a drive for volume {volumeLetter}", nameof(volumeLetter));

var drive = GetDrives(partition["DeviceID"].ToString()!).FirstOrDefault();

if (drive is null)
throw new ArgumentException(
$"Could not find a drive for volume {volumeLetter}", nameof(volumeLetter));

return FromPath(drive["DeviceID"].ToString()!);
}

private static IEnumerable<ManagementBaseObject> GetPartitions(string volumeLetter)
=> ExecuteWmicQuery($@"
ASSOCIATORS OF
{{Win32_LogicalDisk.DeviceID='{volumeLetter}'}}
WHERE
AssocClass = Win32_LogicalDiskToPartition");

private static IEnumerable<ManagementBaseObject> GetDrives(string partitionId)
=> ExecuteWmicQuery($@"
ASSOCIATORS OF
{{Win32_DiskPartition.DeviceID='{partitionId}'}}
WHERE
AssocClass = Win32_DiskDriveToDiskPartition");

private static IEnumerable<ManagementBaseObject> ExecuteWmicQuery(string query)
{
var queryResults = new ManagementObjectSearcher(query);
var result = queryResults.Get();

return result is null
? Enumerable.Empty<ManagementBaseObject>()
: result.OfType<ManagementBaseObject>();
}
}
}
#endif
#pragma warning restore CA1416 // This call site is reachable on all platforms.
30 changes: 30 additions & 0 deletions DiskPartitionInfo/FluentApi/MbrReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.IO;
using DiskPartitionInfo.Extensions;
using DiskPartitionInfo.Mbr;
using DiskPartitionInfo.Models;

namespace DiskPartitionInfo.FluentApi
{
internal partial class MbrReader : IMbrReader
{
private const int MbrLength = 512;

/// <inheritdoc/>
public MasterBootRecord FromPath(string path)
{
using var stream = File.OpenRead(path);

return FromStream(stream);
}

/// <inheritdoc/>
public MasterBootRecord FromStream(Stream stream)
{
var data = new byte[MbrLength];
stream.Read(buffer: data, offset: 0, count: MbrLength);

var mbr = data.ToStruct<ClassicalMasterBootRecord>();
return new MasterBootRecord(mbr);
}
}
}
Loading

0 comments on commit 81bbedf

Please sign in to comment.