Skip to content

Commit

Permalink
Add version and revision options to rdb fs add command to support add…
Browse files Browse the repository at this point in the history
…ing file systems wíthout version string. Fix windows physical drive throwing error when cdrom drive is present. Fix streams being closed by discutils disk. Fix file system writer to create directory before writing entry. Fix fast file system dos 0 read past end of file with file size is equal to data block size
  • Loading branch information
henrikstengaard committed Sep 28, 2023
1 parent 11fbd46 commit 1db452d
Show file tree
Hide file tree
Showing 23 changed files with 339 additions and 137 deletions.
5 changes: 3 additions & 2 deletions src/Hst.Imager.ConsoleApp/CommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,10 +364,11 @@ await Execute(new RdbRestoreCommand(GetLogger<RdbRestoreCommand>(), GetCommandHe
await GetPhysicalDrives(), backupPath, path));
}

public static async Task RdbFsAdd(string path, string fileSystemPath, string dosType, string fileSystemName)
public static async Task RdbFsAdd(string path, string fileSystemPath, string dosType, string fileSystemName,
int? version, int? revision)
{
await Execute(new RdbFsAddCommand(GetLogger<RdbFsAddCommand>(), GetCommandHelper(),
await GetPhysicalDrives(), path, fileSystemPath, dosType, fileSystemName));
await GetPhysicalDrives(), path, fileSystemPath, dosType, fileSystemName, version, revision));
}

public static async Task RdbFsDel(string path, int fileSystemNumber)
Expand Down
14 changes: 12 additions & 2 deletions src/Hst.Imager.ConsoleApp/RdbCommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,23 @@ private static Command CreateRdbFsAdd()
new[] { "--name", "-n" },
description: "Name of file system.");

var versionOption = new Option<int?>(
new[] { "--version", "-v" },
description: "Version of file system (number before . in version).");

var revisionOption = new Option<int?>(
new[] { "--revision", "-r" },
description: "Revision of file system (number after . in version).");

var rdbFsAddCommand = new Command("add", "Add file system.");
rdbFsAddCommand.SetHandler(CommandHandler.RdbFsAdd, pathArgument, fileSystemPathArgument, dosTypeArgument,
fileSystemNameOption);
rdbFsAddCommand.SetHandler(CommandHandler.RdbFsAdd, pathArgument, fileSystemPathArgument,
dosTypeArgument, fileSystemNameOption, versionOption, revisionOption);
rdbFsAddCommand.AddArgument(pathArgument);
rdbFsAddCommand.AddArgument(fileSystemPathArgument);
rdbFsAddCommand.AddArgument(dosTypeArgument);
rdbFsAddCommand.AddOption(fileSystemNameOption);
rdbFsAddCommand.AddOption(versionOption);
rdbFsAddCommand.AddOption(revisionOption);

return rdbFsAddCommand;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Hst.Imager.Core.Tests/CommandTests/FsCommandTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ protected async Task CreateMbrDisk(TestCommandHelper testCommandHelper, string p

var disk = media is DiskMedia diskMedia
? diskMedia.Disk
: new DiscUtils.Raw.Disk(media.Stream, Ownership.Dispose);
: new DiscUtils.Raw.Disk(media.Stream, Ownership.None);

BiosPartitionTable.Initialize(disk);
}
Expand All @@ -60,7 +60,7 @@ protected async Task CreateGptDisk(TestCommandHelper testCommandHelper, string p

var disk = media is DiskMedia diskMedia
? diskMedia.Disk
: new DiscUtils.Raw.Disk(media.Stream, Ownership.Dispose);
: new DiscUtils.Raw.Disk(media.Stream, Ownership.None);

GuidPartitionTable.Initialize(disk.Content, Geometry.FromCapacity(disk.Capacity));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,23 @@ public async Task WhenCopyingAllRecursivelyFromDiskToLocalDirectoryThenDirectori

// assert - file1.txt file was extracted
var file1 = Path.Combine(destPath, "file1.txt");
Assert.Equal(file1, files.FirstOrDefault(x => x.Equals(file1, StringComparison.OrdinalIgnoreCase))?.ToLowerInvariant());
Assert.Equal(file1,
files.FirstOrDefault(x => x.Equals(file1, StringComparison.OrdinalIgnoreCase))?.ToLowerInvariant());

// assert - file2.txt file was extracted
var file2 = Path.Combine(destPath, "file2.txt");
Assert.Equal(file2, files.FirstOrDefault(x => x.Equals(file2, StringComparison.OrdinalIgnoreCase))?.ToLowerInvariant());
Assert.Equal(file2,
files.FirstOrDefault(x => x.Equals(file2, StringComparison.OrdinalIgnoreCase))?.ToLowerInvariant());

// assert - file3.txt file was extracted
var file3 = Path.Combine(destPath, "dir1", "file3.txt");
Assert.Equal(file3, files.FirstOrDefault(x => x.Equals(file3, StringComparison.OrdinalIgnoreCase))?.ToLowerInvariant());
Assert.Equal(file3,
files.FirstOrDefault(x => x.Equals(file3, StringComparison.OrdinalIgnoreCase))?.ToLowerInvariant());

// assert - test.txt file was extracted
var test = Path.Combine(destPath, "dir1", "test.txt");
Assert.Equal(test, files.FirstOrDefault(x => x.Equals(test, StringComparison.OrdinalIgnoreCase))?.ToLowerInvariant());
Assert.Equal(test,
files.FirstOrDefault(x => x.Equals(test, StringComparison.OrdinalIgnoreCase))?.ToLowerInvariant());
}
finally
{
Expand All @@ -83,8 +87,8 @@ private async Task CreateDirectoriesAndFiles(TestCommandHelper testCommandHelper

using var media = mediaResult.Value;
var stream = media.Stream;
using var disk = media is DiskMedia diskMedia ? diskMedia.Disk : new DiscUtils.Raw.Disk(stream, Ownership.None);

var disk = media is DiskMedia diskMedia ? diskMedia.Disk : new DiscUtils.Raw.Disk(stream, Ownership.None);
var biosPartitionTable = new BiosPartitionTable(disk);
var partition = biosPartitionTable.Partitions.FirstOrDefault();

Expand Down Expand Up @@ -117,4 +121,93 @@ private async Task CreateDirectoriesAndFiles(TestCommandHelper testCommandHelper
{
}
}

[Fact]
public async Task When_CopyFromLocalSubDirectoryToDiskSubDirectory_Then_DirectoryIsCreatedAndFilesCopied()
{
var srcPath = $"{Guid.NewGuid()}";
var destPath = $"{Guid.NewGuid()}.img";

try
{
// arrange - test command helper
var testCommandHelper = new TestCommandHelper();

// arrange - source local directory and files
var dir1Path = Path.Combine(srcPath, "src");
Directory.CreateDirectory(dir1Path);
var file1Path = Path.Combine(dir1Path, "file1.bin");
await File.WriteAllBytesAsync(file1Path, new byte[50000]);
var file2Path = Path.Combine(dir1Path, "file2.bin");
await File.WriteAllBytesAsync(file2Path, new byte[250000]);

// arrange - destination mbr fat formatted disk
testCommandHelper.AddTestMedia(destPath, 10.MB());
await CreateMbrFatFormattedDisk(testCommandHelper, destPath, 4.GB());

// arrange - create fs copy command
var cancellationTokenSource = new CancellationTokenSource();
var fsCopyCommand = new FsCopyCommand(new NullLogger<FsCopyCommand>(), testCommandHelper,
new List<IPhysicalDrive>(),
Path.Combine(srcPath, "src"), Path.Combine(destPath, "mbr", "1", "dest"), true, false, true);

// act - copy directories and files
var result = await fsCopyCommand.Execute(cancellationTokenSource.Token);
Assert.Equal(string.Empty, result.Error?.ToString() ?? string.Empty);
Assert.True(result.IsSuccess);

// arrange - get fat file system
var mediaResult = await testCommandHelper.GetReadableMedia(
Enumerable.Empty<IPhysicalDrive>(), destPath);
using var media = mediaResult.Value;
media.Stream.Position = 0;
var disk = media is DiskMedia diskMedia
? diskMedia.Disk
: new DiscUtils.Raw.Disk(media.Stream,
Ownership.None);
var biosPartitionTable = new BiosPartitionTable(disk);
var partitionStream = biosPartitionTable.Partitions[0].Open();
var fatFileSystem = new FatFileSystem(partitionStream);

// assert - 1 directory in root directory
var dirs = fatFileSystem.GetDirectories("").ToList();
Assert.Single(dirs);

// assert - "dest" exists in root directory
var destDirPath = "dest";
Assert.Equal(destDirPath,
dirs.FirstOrDefault(x => x.Equals(destDirPath, StringComparison.OrdinalIgnoreCase))
?.ToLowerInvariant());

// assert - 0 files in root directory
var files = fatFileSystem.GetFiles("").ToList();
Assert.Empty(files);

// assert - 0 directories in "dest" directory
dirs = fatFileSystem.GetDirectories(destDirPath).ToList();
Assert.Empty(dirs);

// assert - 2 files in root directory
files = fatFileSystem.GetFiles(destDirPath).ToList();
Assert.Equal(2, files.Count);

// assert - file1.bin file was copied and has size 50000 bytes
file1Path = Path.Combine(destDirPath, "file1.bin");
Assert.Equal(file1Path,
files.FirstOrDefault(x => x.Equals(file1Path, StringComparison.OrdinalIgnoreCase))?.ToLowerInvariant());
var file1Info = fatFileSystem.GetFileInfo(file1Path);
Assert.Equal(50000, file1Info.Length);

// assert - file2.bin file was copied and has size 250000 bytes
file2Path = Path.Combine(destDirPath, "file2.bin");
Assert.Equal(file2Path,
files.FirstOrDefault(x => x.Equals(file2Path, StringComparison.OrdinalIgnoreCase))?.ToLowerInvariant());
var file2Info = fatFileSystem.GetFileInfo(file2Path);
Assert.Equal(250000, file2Info.Length);
}
finally
{
DeletePaths(srcPath, destPath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ private async Task CreateMbrNtfsDirectoriesAndFiles(TestCommandHelper testComman
using var media = mediaResult.Value;
var stream = media.Stream;

using var disk = media is DiskMedia diskMedia ? diskMedia.Disk : new DiscUtils.Raw.Disk(stream, Ownership.None);
var disk = media is DiskMedia diskMedia
? diskMedia.Disk
: new DiscUtils.Raw.Disk(stream, Ownership.None);
var biosPartitionTable = new BiosPartitionTable(disk);
var partition = biosPartitionTable.Partitions.FirstOrDefault();

Expand Down
110 changes: 110 additions & 0 deletions src/Hst.Imager.Core.Tests/CommandTests/GivenRdbFsAddCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Hst.Amiga.RigidDiskBlocks;
using Hst.Core.Extensions;
using Hst.Imager.Core.Commands;
using Hst.Imager.Core.Models;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;

namespace Hst.Imager.Core.Tests.CommandTests;

public class GivenRdbFsAddCommand : FsCommandTestBase
{
[Fact]
public async Task When_FileSystemDoesHaveVersionStringAndVersionAndRevisionIsSet_Then_FileSystemIsAdded()
{
// arrange - path, size and test command helper
var imgPath = $"{Guid.NewGuid()}.img";
var testCommandHelper = new TestCommandHelper();
var diskSize = 100.MB();
var fileSystemPath = "FastFileSystem";

// arrange - create rdb disk
testCommandHelper.AddTestMedia(imgPath, diskSize);
await CreateRdbDisk(testCommandHelper, imgPath, diskSize);

// arrange - file system without version string
await testCommandHelper.AddTestMedia(fileSystemPath, fileSystemPath, data: new byte[36]);

// arrange - rdb file system add command
var cancellationTokenSource = new CancellationTokenSource();
var command = new RdbFsAddCommand(new NullLogger<RdbFsAddCommand>(), testCommandHelper,
new List<IPhysicalDrive>(), imgPath, "FastFileSystem", "DOS3",
"FastFileSystem", 1, 0);

// act - execute rdb file system add command
var result = await command.Execute(cancellationTokenSource.Token);
Assert.True(result.IsSuccess);
}

[Fact]
public async Task When_FileSystemDoesHaveVersionStringAndVersionIsNotSet_Then_ErrorIsReturned()
{
// arrange - path, size and test command helper
var imgPath = $"{Guid.NewGuid()}.img";
var testCommandHelper = new TestCommandHelper();
var diskSize = 100.MB();
var fileSystemPath = "FastFileSystem";

// arrange - create rdb disk
testCommandHelper.AddTestMedia(imgPath, diskSize);
await CreateRdbDisk(testCommandHelper, imgPath, diskSize);

// arrange - file system without version string
await testCommandHelper.AddTestMedia(fileSystemPath, fileSystemPath, data: new byte[36]);

// arrange - rdb file system add command
var cancellationTokenSource = new CancellationTokenSource();
var command = new RdbFsAddCommand(new NullLogger<RdbFsAddCommand>(), testCommandHelper,
new List<IPhysicalDrive>(), imgPath, "FastFileSystem", "DOS3",
"FastFileSystem", null, null);

// act - execute rdb file system add command
var result = await command.Execute(cancellationTokenSource.Token);
Assert.False(result.IsSuccess);
Assert.True(result.IsFaulted);
Assert.IsType<VersionNotFoundError>(result.Error);
}

[Fact]
public async Task When_FileSystemDoesHaveVersionStringAndRevisionIsNotSet_Then_ErrorIsReturned()
{
// arrange - path, size and test command helper
var imgPath = $"{Guid.NewGuid()}.img";
var testCommandHelper = new TestCommandHelper();
var diskSize = 100.MB();
var fileSystemPath = "FastFileSystem";

// arrange - create rdb disk
testCommandHelper.AddTestMedia(imgPath, diskSize);
await CreateRdbDisk(testCommandHelper, imgPath, diskSize);

// arrange - file system without version string
await testCommandHelper.AddTestMedia(fileSystemPath, fileSystemPath, data: new byte[36]);

// arrange - rdb file system add command
var cancellationTokenSource = new CancellationTokenSource();
var command = new RdbFsAddCommand(new NullLogger<RdbFsAddCommand>(), testCommandHelper,
new List<IPhysicalDrive>(), imgPath, "FastFileSystem", "DOS3",
"FastFileSystem", 1, null);

// act - execute rdb file system add command
var result = await command.Execute(cancellationTokenSource.Token);
Assert.False(result.IsSuccess);
Assert.True(result.IsFaulted);
Assert.IsType<VersionNotFoundError>(result.Error);
}

private static async Task CreateRdbDisk(TestCommandHelper testCommandHelper, string path, long diskSize)
{
var mediaResult = await testCommandHelper.GetWritableFileMedia(path, diskSize, true);
using var media = mediaResult.Value;
var stream = media is DiskMedia diskMedia ? diskMedia.Disk.Content : media.Stream;

var rigidDiskBlock = RigidDiskBlock.Create(diskSize.ToSectorSize());
await RigidDiskBlockWriter.WriteBlock(rigidDiskBlock, stream);
}
}
2 changes: 1 addition & 1 deletion src/Hst.Imager.Core.Tests/GivenReadCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ public async Task WhenReadPhysicalDriveToVhdWithSizeNotDividableBy512ThenReadDat
// assert - source bytes are equal to destination bytes
destinationBytes = destinationBytes.Take(size).ToArray();
Assert.Equal(sourceBytes.Length, destinationBytes.Length);
Assert.Equal(sourceBytes, destinationBytes);
Assert.True(sourceBytes.SequenceEqual(destinationBytes));
}
finally
{
Expand Down
1 change: 1 addition & 0 deletions src/Hst.Imager.Core.Tests/TestMedia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public async Task WriteData(byte[] data)
{
Stream.Seek(0, SeekOrigin.Begin);
await Stream.WriteBytes(data);
Stream.Seek(0, SeekOrigin.Begin);
}

public async Task<byte[]> ReadData()
Expand Down
18 changes: 17 additions & 1 deletion src/Hst.Imager.Core/Commands/FileSystemEntryWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class FileSystemEntryWriter : IEntryWriter
private readonly Media media;
private readonly string[] pathComponents;
private readonly IFileSystem fileSystem;
private string[] currentPathComponents;
private bool disposed;

public FileSystemEntryWriter(Media media, IFileSystem fileSystem, string[] pathComponents)
Expand All @@ -23,6 +24,7 @@ public FileSystemEntryWriter(Media media, IFileSystem fileSystem, string[] pathC
this.media = media;
this.pathComponents = pathComponents;
this.fileSystem = fileSystem;
this.currentPathComponents = Array.Empty<string>();
}

public string MediaPath => this.media.Path;
Expand All @@ -49,7 +51,7 @@ private void Dispose(bool disposing)
}

public void Dispose() => Dispose(true);

public Task CreateDirectory(Entry entry, string[] entryPathComponents, bool skipAttributes)
{
var fullPathComponents = pathComponents.Concat(entryPathComponents).ToArray();
Expand All @@ -59,12 +61,26 @@ public Task CreateDirectory(Entry entry, string[] entryPathComponents, bool skip
fileSystem.CreateDirectory(string.Join("\\", fullPathComponents.Take(i)));
}

currentPathComponents = fullPathComponents;

return Task.CompletedTask;
}

public async Task WriteEntry(Entry entry, string[] entryPathComponents, Stream stream, bool skipAttributes)
{
var fullPathComponents = pathComponents.Concat(entryPathComponents).ToArray();

var directoryChanged = currentPathComponents.Length != fullPathComponents.Length - 1;
if (directoryChanged && fullPathComponents.Length > 1)
{
for (var i = 1; i < fullPathComponents.Length; i++)
{
fileSystem.CreateDirectory(string.Join("\\", fullPathComponents.Take(i)));
}

currentPathComponents = fullPathComponents.Take(fullPathComponents.Length - 1).ToArray();
}

var fullPath = string.Join("\\", fullPathComponents);

await using var entryStream = fileSystem.OpenFile(fullPath, FileMode.OpenOrCreate);
Expand Down
Loading

0 comments on commit 1db452d

Please sign in to comment.