diff --git a/proprietary/DiskImage/Filesystem/Unknown.cs b/proprietary/DiskImage/Filesystem/Unknown.cs new file mode 100644 index 000000000..81ba3bd79 --- /dev/null +++ b/proprietary/DiskImage/Filesystem/Unknown.cs @@ -0,0 +1,106 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Proprietary.DiskImage.Partition; + +namespace Duplicati.Proprietary.DiskImage.Filesystem; + +public sealed record UnkownFilesystemMetadata +{ + public int BlockSize { get; init; } +} + +public class UnknownFilesystemFile : IFile +{ + public string? Path => null; + public long? Address { get; init; } + public long Size { get; init; } + public bool IsDirectory => false; +} + +public class UnknownFilesystem : IFilesystem +{ + public FileSystemType Type => FileSystemType.Unknown; + + public IPartition Partition { get => m_partition; } + + private int m_blockSize; + private bool m_disposed = false; + private readonly IPartition m_partition; + + public UnknownFilesystem(IPartition partition, int blockSize = 1024 * 1024 * 1024) + { + m_partition = partition; + if (blockSize < 0 || blockSize % partition.PartitionTable.RawDisk?.SectorSize != 0) + throw new ArgumentException("Block size must be non-negative and a multiple of the partition sector size.", nameof(blockSize)); + m_blockSize = blockSize; + } + + public void Dispose() + { + if (m_disposed) + return; + + m_disposed = true; + } + + public Task GetFilesystemMetadataAsync(CancellationToken cancellationToken) + { + var cfg = new UnkownFilesystemMetadata + { + BlockSize = m_blockSize, + }; + + return Task.FromResult(cfg); + } + + public async IAsyncEnumerable ListFilesAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + long totalSize = m_partition.Size; + long blockSize = m_blockSize; + long blockCount = (totalSize + blockSize - 1) / blockSize; + + for (long i = 0; i < blockCount; i++) + { + long address = m_partition.StartOffset + i * blockSize; + long size = Math.Min(blockSize, totalSize - i * blockSize); + yield return new UnknownFilesystemFile() + { + Address = address, + Size = size + }; + } + } + + public async IAsyncEnumerable ListFilesAsync(IFile directory, [EnumeratorCancellation] CancellationToken cancellationToken) + { + if (directory is not UnknownFilesystemFile) + throw new ArgumentException("The specified directory does not belong to this filesystem.", nameof(directory)); + if (!directory.IsDirectory) + throw new ArgumentException("The specified file is not a directory.", nameof(directory)); + + yield break; + } + + public async Task OpenFileAsync(IFile file, CancellationToken cancellationToken) + { + if (file is not UnknownFilesystemFile) + throw new ArgumentException("The specified file does not belong to this filesystem.", nameof(file)); + + if (m_partition.PartitionTable.RawDisk == null) + throw new InvalidOperationException("Cannot read from partition without a raw disk."); + + long address = file.Address ?? throw new ArgumentException("The specified file does not have a valid address.", nameof(file)); + + var startSector = address / m_partition.PartitionTable.RawDisk.SectorSize; + var sectorCount = (file.Size + m_partition.PartitionTable.RawDisk.SectorSize - 1) / m_partition.PartitionTable.RawDisk.SectorSize; + using var partitionStream = await m_partition.PartitionTable.RawDisk.ReadSectorsAsync(startSector, (int)sectorCount, cancellationToken); + var buffer = new byte[file.Size]; + await partitionStream.ReadExactlyAsync(buffer, 0, buffer.Length, cancellationToken); + return new MemoryStream(buffer); + } +} \ No newline at end of file diff --git a/proprietary/FullDiskRunner/Program.cs b/proprietary/FullDiskRunner/Program.cs index ab7555322..e82e4403f 100644 --- a/proprietary/FullDiskRunner/Program.cs +++ b/proprietary/FullDiskRunner/Program.cs @@ -1,27 +1,25 @@ using Duplicati.Proprietary.DiskImage; +using Duplicati.Proprietary.DiskImage.Filesystem; using Duplicati.Proprietary.DiskImage.Partition; // TODO this project is just a test project to test the disk image library. Remove later, or elevate to a proper tool. -/* -using var disk = new Duplicati.Proprietary.DiskImage.Raw.Windows(@"\\.\PhysicalDrive0"); +// Check if we're on Windows platform +if (Environment.OSVersion.Platform != PlatformID.Win32NT) +{ + Console.WriteLine("This tool currently only supports Windows platform."); + return; +} + +using var disk = new Duplicati.Proprietary.DiskImage.Disk.Windows(@"\\.\PhysicalDrive0"); var initialized = await disk.InitializeAsync(CancellationToken.None); if (!initialized) { Console.WriteLine("Failed to initialize disk."); return; } -using var partition = await disk.ReadBytesAsync(0, 4096, CancellationToken.None); -byte[] buffer = new byte[4096]; -await partition.ReadExactlyAsync(buffer, CancellationToken.None); -await File.WriteAllBytesAsync("partition0.bin", buffer); -*/ - -var bytes = await File.ReadAllBytesAsync("partition1.bin"); - -// Use the factory for auto-detection of partition table type -var table = await PartitionTableFactory.CreateAsync(bytes, 512, CancellationToken.None); +var table = await PartitionTableFactory.CreateAsync(disk, CancellationToken.None); if (table == null) { Console.WriteLine("Failed to detect partition table type."); @@ -45,3 +43,29 @@ if (table.TableType == PartitionTableType.GPT) using var mbrStream = await table.GetProtectiveMbrAsync(CancellationToken.None); Console.WriteLine($"Protective MBR retrieved successfully ({mbrStream.Length} bytes)."); } + +var firstPartition = await table.GetPartitionAsync(0, CancellationToken.None); + +if (firstPartition == null) +{ + Console.WriteLine("No partitions found on the disk."); + return; +} + +using var fs = new UnknownFilesystem(firstPartition); + +var filecount = await fs.ListFilesAsync(CancellationToken.None).CountAsync(); + +Console.WriteLine($"Listed {filecount} files in the unknown filesystem of the first partition."); + +if (filecount == 0) return; + +var firstFile = await fs.ListFilesAsync(CancellationToken.None).FirstAsync(); + +using var fileStream = await fs.OpenFileAsync(firstFile, CancellationToken.None); + +// Copy to a buffer +var buffer = new byte[firstFile.Size]; +await fileStream.ReadExactlyAsync(buffer, 0, buffer.Length, CancellationToken.None); + +Console.WriteLine($"Read {buffer.Length} bytes from the first file in the unknown filesystem."); \ No newline at end of file