diff --git a/DiscUtils.Ebs/DiskExtent.cs b/DiscUtils.Ebs/DiskExtent.cs index 4b32be1..48ce08d 100644 --- a/DiscUtils.Ebs/DiskExtent.cs +++ b/DiscUtils.Ebs/DiskExtent.cs @@ -22,11 +22,11 @@ public DiskExtent(DiskImageFile ebsDisk) { while (response == null || response.NextToken != null) { - response = EbsDisk.EbsClient.ListSnapshotBlocks(new ListSnapshotBlocksRequest { + response = EbsDisk.EbsClient.ListSnapshotBlocksAsync(new ListSnapshotBlocksRequest { MaxResults = 500, SnapshotId = EbsDisk.SnapshotId, NextToken = response?.NextToken - }); + }).GetAwaiter().GetResult(); foreach(var block in response.Blocks) { SnapshotBlocks[block.BlockIndex] = new Block(block.BlockIndex, block.BlockToken); diff --git a/DiscUtils.Ebs/DiskImageFile.cs b/DiscUtils.Ebs/DiskImageFile.cs index 2721292..7784e37 100644 --- a/DiscUtils.Ebs/DiskImageFile.cs +++ b/DiscUtils.Ebs/DiskImageFile.cs @@ -30,10 +30,10 @@ public DiskImageFile(string snapshotId, AWSCredentials credentials, RegionEndpoi SnapshotId = snapshotId; EbsClient = new AmazonEBSClient(credentials.GetCredentials().AccessKey, credentials.GetCredentials().SecretKey, region); - var result = EbsClient.ListSnapshotBlocks(new ListSnapshotBlocksRequest() { + var result = EbsClient.ListSnapshotBlocksAsync(new ListSnapshotBlocksRequest() { MaxResults = 1, SnapshotId = snapshotId - }); + }).GetAwaiter().GetResult(); if (result.HttpStatusCode != HttpStatusCode.OK) throw new AmazonEBSException($"Failed to query EBS block with error {result.HttpStatusCode}"); diff --git a/DiscUtils.Ebs/EbsMappedStream.cs b/DiscUtils.Ebs/EbsMappedStream.cs index 6303b37..89aa785 100644 --- a/DiscUtils.Ebs/EbsMappedStream.cs +++ b/DiscUtils.Ebs/EbsMappedStream.cs @@ -45,12 +45,12 @@ public EbsMappedStream(string snapshotId, AWSCredentials credentials, RegionEndp while (response == null || response.NextToken != null) { - response = EbsClient.ListSnapshotBlocks(new ListSnapshotBlocksRequest { + response = EbsClient.ListSnapshotBlocksAsync(new ListSnapshotBlocksRequest { MaxResults = 500, SnapshotId = SnapshotId, StartingBlockIndex = 0, NextToken = response?.NextToken - }); + }).GetAwaiter().GetResult(); if (response.HttpStatusCode != HttpStatusCode.OK) throw new AmazonEBSException($"Failed to query EBS block with error {response.HttpStatusCode}"); @@ -94,11 +94,11 @@ public override int Read(byte[] buffer, int offset, int count) { void GetBlockData(Block block, byte[] buffer, int offset) { - var result = EbsClient.GetSnapshotBlock(new GetSnapshotBlockRequest() { + var result = EbsClient.GetSnapshotBlockAsync(new GetSnapshotBlockRequest() { BlockIndex = block.Index, BlockToken = block.Token, SnapshotId = SnapshotId - }); + }).GetAwaiter().GetResult(); if(result.HttpStatusCode != System.Net.HttpStatusCode.OK) { throw new IOException($"Failed to read EBS block {block.Index} with HTTP error {result.HttpStatusCode}"); diff --git a/Program.cs b/Program.cs index 78b1328..f93f181 100644 --- a/Program.cs +++ b/Program.cs @@ -49,7 +49,34 @@ static VirtualDisk GetEBSDiskImage(string snapshotId, string profile, string aws } static VirtualDisk GetLocalDiskImage(string path) { - return VirtualDisk.OpenDisk(path, FileAccess.Read); + + // DiscUtils 0.16.13's generic VirtualDisk.OpenDisk(path) routes through a + // file locator that hardcodes a Windows '\' path separator, which breaks on + // Linux/macOS (e.g. "/tmp\/disk.img"). The per-format path constructors use + // a working locator, so dispatch by extension to those instead. Using the + // path (not stream) constructor is important: multi-extent disks (e.g. a + // VMDK descriptor pointing at -flat/-s001 extents) need the locator to + // resolve their sibling files. The stream constructor only handles + // self-contained images and throws "Unknown type" / "Only Monolithic + // Sparse ... can be accessed via a stream" for split/flat disks. + var ext = Path.GetExtension(path).ToLowerInvariant(); + + switch (ext) { + case ".vhd": + return new DiscUtils.Vhd.Disk(path, FileAccess.Read); + case ".vhdx": + return new DiscUtils.Vhdx.Disk(path, FileAccess.Read); + case ".vmdk": + return new DiscUtils.Vmdk.Disk(path, FileAccess.Read); + case ".dmg": + // Dmg has no path constructor; it is always a single self-contained file. + return new DiscUtils.Dmg.Disk( + new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read), + Ownership.Dispose); + default: + // .img/.raw/.dd/.bin and anything else: treat as a raw sector image. + return new DiscUtils.Raw.Disk(path, FileAccess.Read); + } } @@ -160,19 +187,19 @@ static void Main(string[] args) { foreach (var file in fs.GetDirectories(filePath)) { var dirInfo = fs.GetDirectoryInfo(file); - Console.WriteLine($"{dirInfo.LastWriteTimeUtc} {"DIR",-15} {Path.GetFileName(file)}"); + Console.WriteLine($"{dirInfo.LastWriteTimeUtc} {"DIR",-15} {LeafName(file)}"); } foreach (var file in fs.GetFiles(filePath)) { var fileInfo = fs.GetFileInfo(file); - Console.WriteLine($"{fileInfo.LastWriteTimeUtc} { $"{fileInfo.Length / 1024.0 / 1024.0: 0.00}MB",-16} {Path.GetFileName(file)}"); + Console.WriteLine($"{fileInfo.LastWriteTimeUtc} { $"{fileInfo.Length / 1024.0 / 1024.0: 0.00}MB",-16} {LeafName(file)}"); } } else { var fileStream = fs.OpenFile(filePath, FileMode.Open, FileAccess.Read); Console.WriteLine($"[+] Opened file with path {filePath} for with size: {fileStream.Length}"); - File.WriteAllBytes(Path.GetFileName(filePath), StreamUtilities.ReadExact(fileStream, (int)fileStream.Length)); + File.WriteAllBytes(LeafName(filePath), StreamUtilities.ReadExact(fileStream, (int)fileStream.Length)); } } } else { @@ -255,7 +282,7 @@ private static void UI_ItemChanged(string itemValue) { static private async Task DownloadFile(string volumePath) { using (var fileStream = CurrentFileSystem.OpenFile(volumePath, FileMode.Open, FileAccess.Read)) { - using (var outputStream = new FileStream(Path.GetFileName(volumePath), FileMode.OpenOrCreate, FileAccess.ReadWrite)) { + using (var outputStream = new FileStream(LeafName(volumePath), FileMode.OpenOrCreate, FileAccess.ReadWrite)) { byte[] buffer = new byte[512 * 1024]; long totalRead = 0; @@ -340,6 +367,18 @@ private static void Ui_ItemSelected(string itemValue) { } } + // DiscUtils returns in-disk paths using the volume's separator (e.g. '\' for + // NTFS/FAT). Path.GetFileName only strips the host OS separator, so on Linux it + // leaves the whole "\Windows\..." path intact in listings and download output + // filenames. Take the leaf after either separator so behaviour matches Windows. + static string LeafName(string path) { + if (string.IsNullOrEmpty(path)) { + return path; + } + int idx = path.LastIndexOfAny(new[] { '\\', '/' }); + return idx < 0 ? path : path.Substring(idx + 1); + } + static string GetHumanSize(long size) { if (size < 1024) { return $"{size}"; @@ -356,8 +395,8 @@ static string GetHumanSize(long size) { private static void UpdateView() { UI.UpdateCurrentPathItems(new string[] { ".." } - .Concat(CurrentFileSystem.GetDirectories(CurrentPath).Select(pi => Path.GetFileName(pi)) - .Concat(CurrentFileSystem.GetFiles(CurrentPath)).Select(pi => Path.GetFileName(pi))) + .Concat(CurrentFileSystem.GetDirectories(CurrentPath).Select(pi => LeafName(pi)) + .Concat(CurrentFileSystem.GetFiles(CurrentPath)).Select(pi => LeafName(pi))) .ToList()); } } diff --git a/Volumiser.csproj b/Volumiser.csproj index fcd4fa6..6ac896b 100644 --- a/Volumiser.csproj +++ b/Volumiser.csproj @@ -2,7 +2,8 @@ Exe - net48 + net8.0 + true @@ -11,10 +12,6 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive -