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
-