Browse Source

commit: fix ramdisk writeblocks; add ntfs format and create temp folder for ramdisk

HOME 3 years ago
parent
commit
f43d48042f

+ 2 - 0
SimpleVirtualDrive.sln.DotSettings

@@ -0,0 +1,2 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Ntfs/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 2 - 2
SvdCli/App/SvdService.cs

@@ -7,12 +7,12 @@ namespace SvdCli.App
 {
     internal class SvdService
     {
-        private readonly IStorage _storage;
+        private readonly ISvdStorage _storage;
         private readonly EndPoint _serverEndPoint;
 
         private readonly VirtualDrive _virtualDrive;
 
-        public SvdService(IStorage storage, EndPoint serverEndPoint)
+        public SvdService(ISvdStorage storage, EndPoint serverEndPoint)
         {
             _storage = storage ?? throw new ArgumentNullException(nameof(storage));
             _serverEndPoint = serverEndPoint;

+ 20 - 0
SvdCli/BlockDifferencingDiskImage/Format.txt

@@ -0,0 +1,20 @@
+Sln Rev2
+======
+16KB info area
+16KB Aligned index entries block
+16KB ** Data blocks
+
+Info area
+------
+4: Fixed `BDD2' signature
+1: LEU16 prefix of base image file path UTF-8
+0-255: ...
+4: LEU32 Size of block
+4: LEU32 Number of block
+...: Padding to 64KB
+
+Index entry
+------
+8: LEU32 Offset to file
+1: Flags [Allocated,Trimed,...]
+

+ 80 - 18
SvdCli/CliProgram.cs

@@ -11,6 +11,8 @@ using System.Reflection;
 using System.Security;
 using System.ServiceProcess;
 using System.Text.RegularExpressions;
+using SvdCli.DiskUtils;
+using SvdCli.Storage.Implements;
 
 namespace SvdCli
 {
@@ -28,21 +30,23 @@ namespace SvdCli
                 Console.WriteLine(@"Compact Bdd C:\Path\To\Your\Bdd.bdd");
                 Console.WriteLine(@"");
                 Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> RamDisk 10GB");
+                Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> NtfsRamDisk 10GB");
+                Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> TempNtfsRamDisk 10GB");
                 Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> RamDisk 10GB C:\Path\To\Your\Bdd.Bdd [ReadOnly]");
                 Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> Image C:\Path\To\Your\Image.img [ReadOnly]");
-                Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> Image C:\Path\To\Your\Bdd.bdd [ReadOnly]");
+                Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> Bdd C:\Path\To\Your\Bdd.bdd [ReadOnly]");
                 Console.WriteLine(@"[Install ServiceName|Service] Mount Net 192.168.233.233 2333");
                 Console.WriteLine(@"[Install ServiceName|Service] Server 192.168.233.233 2333 Dispatch 64KB C:\Path\To\Your\Image.img C:\Path\To\Your\Dir\");
                 Console.WriteLine(@"");
                 Console.WriteLine(@"Uninstall ServiceName           Safe:Restrict to register by this program");
                 Console.WriteLine(@"");
-                Console.Write("Press ENTER to exit");
+                Console.Write("Press ENTER to exit...");
                 Console.ReadLine();
                 return;
             }
 
             EndPoint serverEndPoint = null;
-            IStorage storage = null;
+            ISvdStorage storage = null;
             switch (args[0].ToLower())
             {
                 case "install":
@@ -114,39 +118,88 @@ namespace SvdCli
                     var svc = new SvdService(storage, serverEndPoint);
                     svc.Start();
                     Console.WriteLine();
-                    Console.Write("Press ENTER to Stop");
+                    Console.Write("Press ENTER to Stop...");
                     Console.ReadLine();
+                    Console.Write("Stopping...");
                     svc.Stop();
+                    Console.Write("Stopped.");
                     break;
             }
         }
 
         private static void ParseCreate(string[] args)
         {
-            //Raw 50GB C:\Path\To\Your\Image.img
-            //Bdd 10GB 64KB C:\Path\To\Your\Bdd.bdd
-            //Bdd 64KB C:\Path\To\Your\Image.img C:\Path\To\Your\Bdd.bdd
+            // 0   1    2    3
+            // Bdd 10GB 64KB C:\Path\To\Your\Bdd.bdd
+            // Bdd 64KB C:\Path\To\Your\Image.img C:\Path\To\Your\Bdd.bdd
+            // Raw 50GB C:\Path\To\Your\Image.img
+
+            if (false == ParseDiskSize(args[1], out var value1, out var unit1)) throw new ArgumentException("Failure to parse disk size");
+            var size1 = CalcDiskSize(value1, unit1);
 
             switch (args[0].ToLower())
             {
                 case "raw":
+                    if (File.Exists(args[2])) throw new IOException("File Already Exist");
+                    {
+                        using var img = File.Create(args[2]);
+                        img.SetSparse();
+                        img.SetLength(size1);
+                    }
+                    break;
+
                 case "bdd":
+                    if (args.Length > 3)
+                    {
+                        if (ParseDiskSize(args[2], out var value2, out var unit2)
+                            && ".bdd" == Path.GetFileName(args[2])?.ToLower()
+                            && false == File.Exists(args[3]))
+                        {
+                            //bdd 2=size 3=bdd[not exist]
+
+                            var size = size1;
+
+                            var blockSize = CalcDiskSize(value2, unit2);
+
+                            throw new NotImplementedException();
+                        }
+
+                        if (".img" == Path.GetExtension(args[2])?.ToLower()
+                            && File.Exists(args[2])
+                            && ".bdd" == Path.GetExtension(args[3])?.ToLower()
+                            && false == File.Exists(args[3]))
+                        {
+                            //snapshot 2=img[exist] 3=bdd[not exist]
+
+                            var blockSize = value1;
+
+                            throw new NotImplementedException();
+                        }
+                    }
+
+                    throw new ArgumentException("Unknown bdd create action");
+
                 default: throw new ArgumentException("Unknown image type");
             }
         }
 
-        private static IStorage ParseMount(string[] args)
+        private static ISvdStorage ParseMount(string[] args)
         {
-            //RamDisk 10GB
-            //RamDisk 10GB C:\Path\To\Your\Bdd.Bdd [ReadOnly]
-            //Image C:\Path\To\Your\Image.img [ReadOnly]
-            //Image C:\Path\To\Your\Bdd.bdd [ReadOnly]
-            //Net 192.168.233.233 2333
-
-            switch (args[0].ToLower())
+            // 0       1    2                       3
+            // RamDisk 10GB C:\Path\To\Your\Bdd.Bdd [ReadOnly]
+            // RamDisk 10GB
+            // NtfsRamDisk 10GB
+            // TempNtfsRamDisk 10GB
+            // Image C:\Path\To\Your\Image.img [ReadOnly]
+            // Bdd C:\Path\To\Your\Bdd.bdd [ReadOnly]
+            // Net 192.168.233.233 2333
+
+            var op = args[0].ToLower();
+            switch (op)
             {
                 case "ramdisk":
-
+                case "ntfsramdisk":
+                case "tempntfsramdisk":
                     if (ParseDiskSize(args[1], out var value, out var unit))
                     {
                         if (false == string.IsNullOrWhiteSpace(unit) && false == unit.StartsWith("g"))
@@ -158,12 +211,21 @@ namespace SvdCli
 
                         if (args.Length > 3 && args[3].ToLower() == "readonly") storage.WriteProtect = true;
 
+
+                        if (op == "ntfsramdisk") FsMaker.MakeNtfsRamDisk(storage, false);
+                        if (op == "tempntfsramdisk") FsMaker.MakeNtfsRamDisk(storage, true);
+
                         return storage;
                     }
 
                     throw new ArgumentException("Failure to parse args");
 
                 case "image":
+                    {
+                        var storage = new RawImageStorage(args[1], args.Length > 2 && "readonly" == args[2].ToLower());
+                        return storage;
+                    }
+                case "bdd":
                 case "net":
                 default:
                     throw new ArgumentOutOfRangeException();
@@ -199,8 +261,8 @@ namespace SvdCli
         {
             switch (unit.ToLower())
             {
-                case "":
-                    return value;
+                //case "":
+                //    return value;
 
                 case "k":
                 case "kb":

+ 80 - 0
SvdCli/DiskUtils/FsMaker.cs

@@ -0,0 +1,80 @@
+using DiscUtils;
+using DiscUtils.Ntfs;
+using DiscUtils.Partitions;
+using DiscUtils.Streams;
+using SvdCli.Storage;
+using System;
+using System.IO;
+
+namespace SvdCli.DiskUtils
+{
+    internal static class FsMaker
+    {
+        public static void MakeNtfsRamDisk(ISvdStorage storage, bool createTempFolder)
+        {
+            storage.Init();
+            using var sss = new SvdStorageStream(storage);
+            using VirtualDisk disk = new DiscUtils.Raw.Disk(sss, Ownership.None);
+            BiosPartitionTable.Initialize(disk, WellKnownPartitionType.WindowsNtfs);
+            var volMgr = new VolumeManager(disk);
+
+            using var destNtfs = NtfsFileSystem.Format(volMgr.GetLogicalVolumes()[0], "RamDIsk", new NtfsFormatOptions());
+            if (createTempFolder) destNtfs.CreateDirectory("Temp");
+        }
+
+        private class SvdStorageStream : Stream
+        {
+            private readonly ISvdStorage _storage;
+
+            public SvdStorageStream(ISvdStorage storage) => _storage = storage;
+
+            public override int Read(byte[] buffer, int offset, int count)
+            {
+                var read = _storage.Read(buffer, Position, offset, count);
+                Position += read;
+                return read;
+            }
+
+            public override void Write(byte[] buffer, int offset, int count)
+            {
+                _storage.Write(buffer, Position, offset, count);
+                Position += count;
+            }
+
+            public override void Flush() => _storage.Flush();
+
+            public override long Seek(long offset, SeekOrigin origin)
+            {
+                switch (origin)
+                {
+                    case SeekOrigin.Begin:
+                        Position = offset;
+                        break;
+
+                    case SeekOrigin.Current:
+                        Position += offset;
+                        break;
+
+                    case SeekOrigin.End:
+                        Position = Length + offset;
+                        break;
+
+                    default:
+                        throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
+                }
+
+                return Position;
+            }
+
+            public override void SetLength(long value)
+            {
+            }
+
+            public override bool CanRead => true;
+            public override bool CanSeek => true;
+            public override bool CanWrite => true;
+            public override long Length => _storage.BlockSize * (long)_storage.BlockCount;
+            public override long Position { get; set; }
+        }
+    }
+}

+ 8 - 7
SvdCli/DriverAdapt/VirtualDrive.cs

@@ -8,13 +8,13 @@ namespace SvdCli.DriverAdapt
 {
     public class VirtualDrive
     {
-        private readonly IStorage _storage;
+        private readonly ISvdStorage _storage;
         private readonly SpdImplement _spdImplement;
         private readonly StorageUnitHost _host;
 
         public long Size { get; }
 
-        public VirtualDrive(IStorage storage)
+        public VirtualDrive(ISvdStorage storage)
         {
             _storage = storage;
             _spdImplement = new SpdImplement(_storage);
@@ -49,19 +49,19 @@ namespace SvdCli.DriverAdapt
 
         private class SpdImplement : StorageUnitBase
         {
-            private readonly IStorage _storage;
+            private readonly ISvdStorage _storage;
 
-            public SpdImplement(IStorage storage) => _storage = storage;
+            public SpdImplement(ISvdStorage storage) => _storage = storage;
 
             public override void Read(byte[] buffer, ulong blockAddress, uint blockCount, bool flush, ref StorageUnitStatus status)
             {
                 if (flush) _storage.Flush();
-                _storage.Read(buffer, blockAddress, blockCount);
+                _storage.ReadBlocks(buffer, blockAddress, blockCount);
             }
 
             public override void Write(byte[] buffer, ulong blockAddress, uint blockCount, bool flush, ref StorageUnitStatus status)
             {
-                _storage.Write(buffer, blockAddress, blockCount);
+                _storage.WriteBlocks(buffer, blockAddress, blockCount);
                 if (flush) _storage.Flush();
             }
 
@@ -72,7 +72,8 @@ namespace SvdCli.DriverAdapt
 
             public override void Unmap(UnmapDescriptor[] descriptors, ref StorageUnitStatus status)
             {
-                _storage.Trim(descriptors.Select(p => new TrimDescriptor(p.BlockAddress, p.BlockCount)).ToArray());
+                var trimDescriptors = descriptors.Select(p => new TrimDescriptor((long)p.BlockAddress * _storage.BlockSize, p.BlockCount * _storage.BlockSize)).ToArray();
+                _storage.Trim(trimDescriptors);
             }
         }
     }

+ 384 - 0
SvdCli/SampleProgram.cs

@@ -0,0 +1,384 @@
+/**
+ * @file Program.cs
+ *
+ * @copyright 2018-2019 Bill Zissimopoulos
+ */
+/*
+ * This file is part of WinSpd.
+ *
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License version 3 as published by the Free Software
+ * Foundation.
+ *
+ * Licensees holding a valid commercial license may use this software
+ * in accordance with the commercial license agreement provided in
+ * conjunction with the software.  The terms and conditions of any such
+ * commercial license agreement shall govern, supersede, and render
+ * ineffective any application of the GPLv3 license to this software,
+ * notwithstanding of any reference thereto in the software or
+ * associated repository.
+ */
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+using Spd;
+using StorageUnitStatus = Spd.Interop.StorageUnitStatus;
+using UnmapDescriptor = Spd.Interop.UnmapDescriptor;
+using Partition = Spd.Interop.Partition;
+
+namespace rawdisk
+{
+    class RawDisk : StorageUnitBase
+    {
+        public const UInt32 MaxTransferLength = 64 * 1024;
+
+        public RawDisk(String RawDiskFile,
+            UInt64 BlockCount, UInt32 BlockLength)
+        {
+            _BlockCount = BlockCount;
+            _BlockLength = BlockLength;
+
+            if (RawDiskFile.StartsWith("\\\\?\\"))
+                RawDiskFile = RawDiskFile.Substring(4);
+
+            _Stream = new FileStream(RawDiskFile,
+                FileMode.OpenOrCreate, FileAccess.ReadWrite, 0, (int)MaxTransferLength, false);
+
+            FILE_SET_SPARSE_BUFFER Sparse;
+            UInt32 BytesTransferred;
+            Sparse.SetSparse = true;
+            _Sparse = DeviceIoControl(_Stream.SafeFileHandle.DangerousGetHandle(),
+                0x900c4/*FSCTL_SET_SPARSE*/,
+                ref Sparse, (UInt32)Marshal.SizeOf(Sparse),
+                IntPtr.Zero, 0, out BytesTransferred, IntPtr.Zero);
+
+            UInt64 FileSize = (UInt64)_Stream.Length;
+            bool ZeroSize = 0 == FileSize;
+            if (ZeroSize)
+                FileSize = BlockCount * BlockLength;
+            if (0 == FileSize || BlockCount * BlockLength != FileSize)
+                throw new ArgumentException();
+            _Stream.SetLength((long)FileSize);
+
+            if (ZeroSize)
+            {
+                Partition[] Partitions = new Partition[1];
+                Byte[] Buffer = new Byte[512];
+
+                Partitions[0].Type = 7;
+                Partitions[0].BlockAddress = 4096 >= BlockLength ? 4096 / BlockLength : 1;
+                Partitions[0].BlockCount = BlockCount - Partitions[0].BlockAddress;
+                if (0 == StorageUnitHost.SpdDefinePartitionTable(Partitions, Buffer))
+                {
+                    _Stream.Position = 0;
+                    _Stream.Write(Buffer, 0, Buffer.Length);
+                    _Stream.Flush();
+                }
+            }
+        }
+
+        public override void Init(Object Host0)
+        {
+            StorageUnitHost Host = (StorageUnitHost)Host0;
+            Host.Guid = Guid.NewGuid();
+            Host.BlockCount = _BlockCount;
+            Host.BlockLength = _BlockLength;
+            Host.MaxTransferLength = MaxTransferLength;
+        }
+
+        public override void Stopped(Object Host0)
+        {
+            _Stream.Dispose();
+        }
+
+        public override void Read(Byte[] Buffer, UInt64 BlockAddress, UInt32 BlockCount, Boolean Flush,
+            ref StorageUnitStatus Status)
+        {
+            if (Flush)
+                this.Flush(BlockAddress, BlockCount, ref Status);
+
+            lock (this) /* I want pread */
+            {
+                _Stream.Position = (long)(BlockAddress * _BlockLength);
+                _Stream.Read(Buffer, 0, (int)(BlockCount * _BlockLength));
+                    /* FIX: we assume that we are reading from a file and ignore the return value */
+            }
+        }
+
+        public override void Write(Byte[] Buffer, UInt64 BlockAddress, UInt32 BlockCount, Boolean Flush,
+            ref StorageUnitStatus Status)
+        {
+            lock (this) /* I want pwrite */
+            {
+                _Stream.Position = (long)(BlockAddress * _BlockLength);
+                _Stream.Write(Buffer, 0, (int)(BlockCount * _BlockLength));
+            }
+
+            if (Flush)
+                this.Flush(BlockAddress, BlockCount, ref Status);
+        }
+
+        public override void Flush(UInt64 BlockAddress, UInt32 BlockCount,
+            ref StorageUnitStatus Status)
+        {
+            _Stream.Flush();
+        }
+
+        public override void Unmap(UnmapDescriptor[] Descriptors,
+            ref StorageUnitStatus Status)
+        {
+            FILE_ZERO_DATA_INFORMATION Zero;
+            UInt32 BytesTransferred;
+            for (int I = 0; Descriptors.Length > I; I++)
+            {
+                Boolean SetZero = false;
+
+                if (_Sparse)
+                {
+                    Zero.FileOffset = (long)(Descriptors[I].BlockAddress * _BlockLength);
+                    Zero.BeyondFinalZero = (long)((Descriptors[I].BlockAddress + Descriptors[I].BlockCount) *
+                        _BlockLength);
+                    SetZero = DeviceIoControl(_Stream.SafeFileHandle.DangerousGetHandle(),
+                        0x980c8/*FSCTL_SET_ZERO_DATA*/,
+                        ref Zero, (UInt32)Marshal.SizeOf(Zero),
+                        IntPtr.Zero, 0, out BytesTransferred, IntPtr.Zero);
+                }
+
+                if (!SetZero)
+                {
+                    lock (this) /* I want pwrite */
+                    {
+                        _Stream.Position = (long)(Descriptors[I].BlockAddress * _BlockLength);
+
+                        int TotalLength = (int)(Descriptors[I].BlockCount * _BlockLength);
+                        Byte[] Buffer = new Byte[Math.Min(64 * 1024, TotalLength)];
+
+                        while (0 < TotalLength)
+                        {
+                            _Stream.Write(Buffer, 0, Buffer.Length);
+                            TotalLength -= Buffer.Length;
+                        }
+                    }
+                }
+            }
+        }
+
+        private UInt64 _BlockCount;
+        private UInt32 _BlockLength;
+        private FileStream _Stream;
+        private Boolean _Sparse;
+
+        /* interop */
+        [StructLayout(LayoutKind.Sequential)]
+        private struct FILE_SET_SPARSE_BUFFER
+        {
+            public Boolean SetSparse;
+        }
+        [StructLayout(LayoutKind.Sequential)]
+        private struct FILE_ZERO_DATA_INFORMATION
+        {
+            public Int64 FileOffset;
+            public Int64 BeyondFinalZero;
+        }
+        [DllImport("kernel32.dll", SetLastError = true)]
+        [return: MarshalAs(UnmanagedType.U1)]
+        private static extern Boolean DeviceIoControl(
+            IntPtr hDevice,
+            UInt32 dwIoControlCode,
+            ref FILE_SET_SPARSE_BUFFER lpInBuffer,
+            UInt32 nInBufferSize,
+            IntPtr lpOutBuffer,
+            UInt32 nOutBufferSize,
+            out UInt32 lpBytesReturned,
+            IntPtr Overlapped);
+        [DllImport("kernel32.dll", SetLastError = true)]
+        [return: MarshalAs(UnmanagedType.U1)]
+        private static extern Boolean DeviceIoControl(
+            IntPtr hDevice,
+            UInt32 dwIoControlCode,
+            ref FILE_ZERO_DATA_INFORMATION lpInBuffer,
+            UInt32 nInBufferSize,
+            IntPtr lpOutBuffer,
+            UInt32 nOutBufferSize,
+            out UInt32 lpBytesReturned,
+            IntPtr Overlapped);
+    }
+
+    class Program
+    {
+        private class CommandLineUsageException : Exception
+        {
+            public CommandLineUsageException(String Message = null) : base(Message)
+            {
+                HasMessage = null != Message;
+            }
+
+            public bool HasMessage;
+        }
+
+        private const String PROGNAME = "rawdisk-dotnet";
+
+        private static void argtos(String[] Args, ref int I, ref String V)
+        {
+            if (Args.Length > ++I)
+                V = Args[I];
+            else
+                throw new CommandLineUsageException();
+        }
+        private static void argtol(String[] Args, ref int I, ref UInt32 V)
+        {
+            Int32 R;
+            if (Args.Length > ++I)
+                V = Int32.TryParse(Args[I], out R) ? (UInt32)R : V;
+            else
+                throw new CommandLineUsageException();
+        }
+
+        static void Main(string[] Args)
+        {
+            try
+            {
+                String RawDiskFile = null;
+                UInt32 BlockCount = 1024 * 1024;
+                UInt32 BlockLength = 512;
+                String ProductId = "RawDisk-dotnet";
+                String ProductRevision = "1.0";
+                UInt32 WriteAllowed = 1;
+                UInt32 CacheSupported = 1;
+                UInt32 UnmapSupported = 1;
+                UInt32 DebugFlags = 0;
+                String DebugLogFile = null;
+                String PipeName = null;
+                StorageUnitHost Host = null;
+                RawDisk RawDisk = null;
+                int I;
+
+                for (I = 0; Args.Length > I; I++)
+                {
+                    String Arg = Args[I];
+                    if ('-' != Arg[0])
+                        break;
+                    switch (Arg[1])
+                    {
+                    case '?':
+                        throw new CommandLineUsageException();
+                    case 'c':
+                        argtol(Args, ref I, ref BlockCount);
+                        break;
+                    case 'C':
+                        argtol(Args, ref I, ref CacheSupported);
+                        break;
+                    case 'd':
+                        argtol(Args, ref I, ref DebugFlags);
+                        break;
+                    case 'D':
+                        argtos(Args, ref I, ref DebugLogFile);
+                        break;
+                    case 'f':
+                        argtos(Args, ref I, ref RawDiskFile);
+                        break;
+                    case 'i':
+                        argtos(Args, ref I, ref ProductId);
+                        break;
+                    case 'l':
+                        argtol(Args, ref I, ref BlockLength);
+                        break;
+                    case 'p':
+                        argtos(Args, ref I, ref PipeName);
+                        break;
+                    case 'r':
+                        argtos(Args, ref I, ref ProductRevision);
+                        break;
+                    case 'U':
+                        argtol(Args, ref I, ref UnmapSupported);
+                        break;
+                    case 'W':
+                        argtol(Args, ref I, ref WriteAllowed);
+                        break;
+                    default:
+                        throw new CommandLineUsageException();
+                    }
+                }
+
+                if (Args.Length > I)
+                    throw new CommandLineUsageException();
+
+                if (null == RawDiskFile)
+                    throw new CommandLineUsageException();
+
+                if (null != DebugLogFile)
+                    if (0 != StorageUnitHost.SetDebugLogFile(DebugLogFile))
+                        throw new CommandLineUsageException("cannot open debug log file");
+
+                Host = new StorageUnitHost(RawDisk = new RawDisk(RawDiskFile, BlockCount, BlockLength));
+                Host.ProductId = ProductId;
+                Host.ProductRevisionLevel = ProductRevision;
+                Host.WriteProtected = 0 == WriteAllowed;
+                Host.CacheSupported = 0 != CacheSupported;
+                Host.UnmapSupported = 0 != UnmapSupported;
+                if (0 != Host.Start(PipeName, DebugFlags))
+                    throw new IOException("cannot start storage unit");
+
+                StorageUnitHost.Log(StorageUnitHost.EVENTLOG_INFORMATION_TYPE,
+                    String.Format(
+                        "{0} -f {1} -c {2} -l {3} -i {4} -r {5} -W {6} -C {7} -U {8}{9}{10}",
+                        PROGNAME,
+                        RawDiskFile,
+                        BlockCount, BlockLength, ProductId, ProductRevision,
+                        0 != WriteAllowed ? 1 : 0,
+                        0 != CacheSupported ? 1 : 0,
+                        0 != UnmapSupported ? 1 : 0,
+                        null != PipeName ? " -p " : "",
+                        null != PipeName ? PipeName : ""));
+
+                Console.CancelKeyPress +=
+                    delegate (Object Sender, ConsoleCancelEventArgs Event)
+                    {
+                        Host.Shutdown();
+                        Event.Cancel = true;
+                    };
+                Host.Wait();
+            }
+            catch (CommandLineUsageException ex)
+            {
+                StorageUnitHost.Log(StorageUnitHost.EVENTLOG_ERROR_TYPE,
+                    String.Format(
+                        "{0}" +
+                        "usage: {1} OPTIONS\n" +
+                        "\n" +
+                        "options:\n" +
+                        "    -f RawDiskFile                      Storage unit data file\n" +
+                        "    -c BlockCount                       Storage unit size in blocks\n" +
+                        "    -l BlockLength                      Storage unit block length\n" +
+                        "    -i ProductId                        1-16 chars\n" +
+                        "    -r ProductRevision                  1-4 chars\n" +
+                        "    -W 0|1                              Disable/enable writes (deflt: enable)\n" +
+                        "    -C 0|1                              Disable/enable cache (deflt: enable)\n" +
+                        "    -U 0|1                              Disable/enable unmap (deflt: enable)\n" +
+                        "    -d -1                               Debug flags\n" +
+                        "    -D DebugLogFile                     Debug log file; - for stderr\n" +
+                        "    -p \\\\.\\pipe\\PipeName                Listen on pipe; omit to use driver\n",
+                        ex.HasMessage ? ex.Message + "\n" : "",
+                        PROGNAME));
+                Environment.ExitCode = 87/*ERROR_INVALID_PARAMETER*/;
+            }
+            catch (Exception ex)
+            {
+                if (ex is TypeInitializationException && null != ex.InnerException)
+                    ex = ex.InnerException;
+
+                StorageUnitHost.Log(StorageUnitHost.EVENTLOG_ERROR_TYPE,
+                    ex.Message);
+
+                int hr = Marshal.GetHRForException(ex);
+                Environment.ExitCode = Marshal.GetHRForException(ex);
+                if ((hr & 0xffff0000) == 0x80070000)
+                    Environment.ExitCode = hr & 0xffff;
+                else
+                    Environment.ExitCode = 574/*ERROR_UNHANDLED_EXCEPTION*/;
+            }
+        }
+    }
+}

+ 7 - 3
SvdCli/Storage/IStorage.cs

@@ -1,6 +1,6 @@
 namespace SvdCli.Storage
 {
-    public interface IStorage
+    public interface ISvdStorage
     {
         string ProductId { get; }
         string ProductVersion { get; }
@@ -16,9 +16,13 @@
 
         void Exit();
 
-        void Read(byte[] buffer, ulong blockIndex, uint blockCount);
+        void ReadBlocks(byte[] buffer, ulong blockIndex, uint blockCount);
 
-        void Write(byte[] buffer, ulong blockIndex, uint blockCount);
+        void WriteBlocks(byte[] buffer, ulong blockIndex, uint blockCount);
+
+        int Read(byte[] buffer, long offset, int index, int count);
+
+        void Write(byte[] buffer, long offset, int index, int count);
 
         void Flush();
 

+ 59 - 16
SvdCli/Storage/RamStorage.cs

@@ -1,20 +1,27 @@
 using System;
 using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.WindowsRuntime;
 
-namespace SvdCli.Storage
+namespace SvdCli.Storage.Implements
 {
-    public class UnmanagedGigabyteRamDisk : IStorage
+    public class UnmanagedGigabyteRamDisk : ISvdStorage
     {
         private readonly IntPtr[] _blocks;
         private bool _isInit;
         public string ProductId { get; } = "Svd RamDisk";
         public string ProductVersion { get; } = "1.0";
-        public string TypeDescription { get; } = "RamDisk";
         public uint BlockSize { get; } = 512;
         public ulong BlockCount { get; }
         public uint MaxTransferLength { get; } = 64 * CapacityUnits.KiloByte;
         public bool SupportTrim { get; } = false;
-        public bool WriteProtect { get; set; } = false;
+        public bool WriteProtect { get; set; }
+
+        public UnmanagedGigabyteRamDisk(int sizeInGigabyte)
+        {
+            _blocks = new IntPtr[sizeInGigabyte];
+            var size = (ulong)sizeInGigabyte * CapacityUnits.Gigabyte;
+            BlockCount = size / BlockSize;
+        }
 
         public void Init()
         {
@@ -47,13 +54,6 @@ namespace SvdCli.Storage
             _isInit = false;
         }
 
-        public UnmanagedGigabyteRamDisk(int sizeInGigabyte)
-        {
-            _blocks = new IntPtr[sizeInGigabyte];
-            var size = (ulong)sizeInGigabyte * CapacityUnits.Gigabyte;
-            BlockCount = size / BlockSize;
-        }
-
         private void ReadInternal(byte[] buffer, int offset, int count, int gigabyteIndex, int gigabyteOffset)
         {
             Marshal.Copy(_blocks[gigabyteIndex] + gigabyteOffset, buffer, offset, count);
@@ -64,7 +64,7 @@ namespace SvdCli.Storage
             Marshal.Copy(buffer, offset, _blocks[gigabyteIndex] + gigabyteOffset, count);
         }
 
-        public void Read(byte[] buffer, ulong blockIndex, uint blockCount)
+        public void ReadBlocks(byte[] buffer, ulong blockIndex, uint blockCount)
         {
             var rawOffset = blockIndex * BlockSize;
             var rawSize = blockCount * BlockSize;
@@ -87,19 +87,20 @@ namespace SvdCli.Storage
             }
         }
 
-        public void Write(byte[] buffer, ulong blockIndex, uint blockCount)
+        public void WriteBlocks(byte[] buffer, ulong blockIndex, uint blockCount)
         {
             var rawOffset = blockIndex * BlockSize;
+            var rawSize = (int)(blockCount * BlockSize);
 
-            for (var i = 0; i < buffer.Length;)
+            for (var i = 0; i < rawSize;)
             {
                 var gigabyteIndex = (int)(rawOffset / CapacityUnits.Gigabyte);
                 var gigabyteOffset = (int)(rawOffset % CapacityUnits.Gigabyte);
                 var bytesToWrite = CapacityUnits.Gigabyte - gigabyteOffset;
 
-                if (i + bytesToWrite > buffer.Length)
+                if (i + bytesToWrite > rawSize)
                 {
-                    bytesToWrite = buffer.Length - i;
+                    bytesToWrite = rawSize - i;
                 }
 
                 WriteInternal(buffer, i, bytesToWrite, gigabyteIndex, gigabyteOffset);
@@ -109,6 +110,48 @@ namespace SvdCli.Storage
             }
         }
 
+        public int Read(byte[] buffer, long offset, int index, int count)
+        {
+            for (var i = 0; i < count;)
+            {
+                var gigabyteIndex = (int)(offset / CapacityUnits.Gigabyte);
+                var gigabyteOffset = (int)(offset % CapacityUnits.Gigabyte);
+                var bytesToRead = CapacityUnits.Gigabyte - gigabyteOffset;
+
+                if (i + bytesToRead > count)
+                {
+                    bytesToRead = (count - i);
+                }
+
+                ReadInternal(buffer, i, bytesToRead, gigabyteIndex, gigabyteOffset);
+
+                i += bytesToRead;
+                offset += bytesToRead;
+            }
+
+            return count;
+        }
+
+        public void Write(byte[] buffer, long offset, int index, int count)
+        {
+            for (var i = 0; i < count;)
+            {
+                var gigabyteIndex = (int)(offset / CapacityUnits.Gigabyte);
+                var gigabyteOffset = (int)(offset % CapacityUnits.Gigabyte);
+                var bytesToWrite = CapacityUnits.Gigabyte - gigabyteOffset;
+
+                if (i + bytesToWrite > count)
+                {
+                    bytesToWrite = count - i;
+                }
+
+                WriteInternal(buffer, i, bytesToWrite, gigabyteIndex, gigabyteOffset);
+
+                i += bytesToWrite;
+                offset += bytesToWrite;
+            }
+        }
+
         public void Flush()
         {
         }

+ 129 - 0
SvdCli/Storage/Implements/RawImageStorage.cs

@@ -0,0 +1,129 @@
+using System;
+using System.IO;
+
+namespace SvdCli.Storage.Implements
+{
+    public class RawImageStorage : ISvdStorage
+    {
+        private readonly string _imageFilePath;
+        private FileStream _fileStream;
+        private bool _sparse;
+
+        public string ProductId { get; } = "Svd RawImage Disk";
+        public string ProductVersion { get; } = "1.0";
+        public uint BlockSize { get; } = 512;
+        public uint MaxTransferLength { get; } = 64 * CapacityUnits.KiloByte;
+        public bool SupportTrim { get; }
+
+        public ulong BlockCount { get; }
+        public bool WriteProtect { get; }
+
+        public RawImageStorage(string imageFilePath, bool readOnly = false)
+        {
+            _imageFilePath = imageFilePath;
+            WriteProtect = readOnly;
+            SupportTrim = !readOnly;
+
+            BlockCount = (ulong)new FileInfo(_imageFilePath).Length / BlockSize;
+        }
+
+        public void Init()
+        {
+            if (null != _fileStream) return;
+
+            var access = WriteProtect ? FileAccess.Read : FileAccess.ReadWrite;
+            _fileStream = new FileStream(_imageFilePath, FileMode.Open, access, FileShare.None, (int)MaxTransferLength);
+            if (false == WriteProtect) _sparse = _fileStream.SetSparse();
+        }
+
+        public void Exit()
+        {
+            if (null == _fileStream) return;
+
+            if (false == WriteProtect)
+            {
+                lock (_fileStream)
+                {
+                    _fileStream.Flush(true);
+                }
+            }
+
+            lock (_fileStream)
+            {
+                _fileStream.Close();
+                _fileStream = null;
+            }
+
+        }
+
+        public void ReadBlocks(byte[] buffer, ulong blockIndex, uint blockCount)
+        {
+            lock (_fileStream)
+            {
+                _fileStream.Position = (long)(blockIndex * BlockSize);
+                var length = (int)(blockCount * BlockSize);
+                var offset = 0;
+                while (length > 0)
+                {
+                    var read = _fileStream.Read(buffer, offset, length);
+                    length -= read;
+                    offset += read;
+                }
+            }
+        }
+
+        public void WriteBlocks(byte[] buffer, ulong blockIndex, uint blockCount)
+        {
+            lock (_fileStream)
+            {
+                _fileStream.Position = (long)(blockIndex * BlockSize);
+                _fileStream.Write(buffer, 0, (int)(blockCount * BlockSize));
+            }
+        }
+
+        public int Read(byte[] buffer, long offset, int index, int count)
+        {
+            lock (_fileStream)
+            {
+                _fileStream.Position = offset;
+                return _fileStream.Read(buffer, index, count);
+            }
+        }
+
+        public void Write(byte[] buffer, long offset, int index, int count)
+        {
+            lock (_fileStream)
+            {
+                _fileStream.Position = offset;
+                _fileStream.Write(buffer, index, count);
+            }
+        }
+
+        public void Flush()
+        {
+            lock (_fileStream) _fileStream.Flush(true);
+        }
+
+        public void Trim(params TrimDescriptor[] descriptors)
+        {
+            lock (_fileStream)
+            {
+                foreach (var trim in descriptors)
+                {
+                    if (_sparse && _fileStream.Trim(trim.Offset, trim.Length)) continue;
+
+                    _fileStream.Position = trim.Offset;
+
+                    var totalLength = (int)trim.Length;
+                    var buffer = new byte[Math.Min(64 * 1024, totalLength)];
+
+                    while (0 < totalLength)
+                    {
+                        _fileStream.Write(buffer, 0, buffer.Length);
+                        totalLength -= buffer.Length;
+                    }
+                }
+            }
+        }
+    }
+}

+ 13 - 0
SvdCli/Storage/Implements/SnapshotBddImageStorage.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SvdCli.Storage.Implements
+{
+    public class SnapshotBddImageStorage
+    {
+        // TODO: SnapshotBddImageStorage
+    }
+}

+ 13 - 0
SvdCli/Storage/Implements/StandaloneBddImageStorage.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SvdCli.Storage.Implements
+{
+    public class StandaloneBddImageStorage
+    {
+        // TODO: StandaloneBddImageStorage
+    }
+}

+ 80 - 0
SvdCli/Storage/NativeIoSupport.cs

@@ -0,0 +1,80 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace SvdCli.Storage
+{
+    internal static class NativeIoSupport
+    {
+        public static bool SetSparse(this FileStream fileStream)
+        {
+            if (fileStream == null) throw new ArgumentNullException(nameof(fileStream));
+            if (fileStream.SafeFileHandle == null) throw new ArgumentException($"{nameof(fileStream.SafeFileHandle)} is null", nameof(fileStream));
+
+            FILE_SET_SPARSE_BUFFER sparse;
+            sparse.SetSparse = true;
+            uint bytesTransferred;
+
+            return DeviceIoControl(fileStream.SafeFileHandle.DangerousGetHandle(),
+                0x900c4/*FSCTL_SET_SPARSE*/,
+                ref sparse, (uint)Marshal.SizeOf(sparse),
+                IntPtr.Zero, 0, out bytesTransferred, IntPtr.Zero);
+        }
+
+        public static bool Trim(this FileStream fileStream, long offset, long length)
+        {
+            if (fileStream == null) throw new ArgumentNullException(nameof(fileStream));
+            if (fileStream.SafeFileHandle == null) throw new ArgumentException($"{nameof(fileStream.SafeFileHandle)} is null", nameof(fileStream));
+
+            FILE_ZERO_DATA_INFORMATION zero;
+            uint bytesTransferred;
+
+            zero.FileOffset = offset;
+            zero.BeyondFinalZero = offset + length;
+
+            return DeviceIoControl(fileStream.SafeFileHandle.DangerousGetHandle(),
+                0x980c8 /* FSCTL_SET_ZERO_DATA */,
+                ref zero, (uint)Marshal.SizeOf(zero),
+                IntPtr.Zero, 0, out bytesTransferred, IntPtr.Zero);
+        }
+
+        /* interop */
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct FILE_SET_SPARSE_BUFFER
+        {
+            public bool SetSparse;
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct FILE_ZERO_DATA_INFORMATION
+        {
+            public long FileOffset;
+            public long BeyondFinalZero;
+        }
+
+        [DllImport("kernel32.dll", SetLastError = true)]
+        [return: MarshalAs(UnmanagedType.U1)]
+        private static extern bool DeviceIoControl(
+            IntPtr hDevice,
+            uint dwIoControlCode,
+            ref FILE_SET_SPARSE_BUFFER lpInBuffer,
+            uint nInBufferSize,
+            IntPtr lpOutBuffer,
+            uint nOutBufferSize,
+            out uint lpBytesReturned,
+            IntPtr overlapped);
+
+        [DllImport("kernel32.dll", SetLastError = true)]
+        [return: MarshalAs(UnmanagedType.U1)]
+        private static extern bool DeviceIoControl(
+            IntPtr hDevice,
+            uint dwIoControlCode,
+            ref FILE_ZERO_DATA_INFORMATION lpInBuffer,
+            uint nInBufferSize,
+            IntPtr lpOutBuffer,
+            uint nOutBufferSize,
+            out uint lpBytesReturned,
+            IntPtr overlapped);
+    }
+}

+ 5 - 5
SvdCli/Storage/TrimDescriptor.cs

@@ -2,17 +2,17 @@
 {
     public class TrimDescriptor
     {
-        public TrimDescriptor(ulong blockIndex, uint blockCount)
+        public TrimDescriptor(long offset, uint length)
         {
-            BlockIndex = blockIndex;
-            BlockCount = blockCount;
+            Offset = offset;
+            Length = length;
         }
 
         public TrimDescriptor()
         {
         }
 
-        public ulong BlockIndex { get; set; }
-        public uint BlockCount { get; set; }
+        public long Offset { get; set; }
+        public uint Length { get; set; }
     }
 }

+ 105 - 2
SvdCli/SvdCli.csproj

@@ -26,6 +26,7 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <Prefer32Bit>false</Prefer32Bit>
+    <LangVersion>8</LangVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <PlatformTarget>AnyCPU</PlatformTarget>
@@ -36,8 +37,102 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <Prefer32Bit>false</Prefer32Bit>
+    <LangVersion>8</LangVersion>
+  </PropertyGroup>
+  <PropertyGroup>
+    <StartupObject>SvdCli.CliProgram</StartupObject>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="DiscUtils, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.0.15.1-ci0002\lib\net45\DiscUtils.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.BootConfig, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.BootConfig.0.15.1-ci0002\lib\net45\DiscUtils.BootConfig.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Btrfs, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Btrfs.0.15.1-ci0002\lib\net45\DiscUtils.Btrfs.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Core, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Core.0.15.1-ci0002\lib\net45\DiscUtils.Core.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Dmg, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Dmg.0.15.1-ci0002\lib\net45\DiscUtils.Dmg.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Ext, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Ext.0.15.1-ci0002\lib\net45\DiscUtils.Ext.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Fat, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Fat.0.15.1-ci0002\lib\net45\DiscUtils.Fat.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.HfsPlus, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.HfsPlus.0.15.1-ci0002\lib\net45\DiscUtils.HfsPlus.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Iscsi, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Iscsi.0.15.1-ci0002\lib\net45\DiscUtils.Iscsi.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Iso9660, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Iso9660.0.15.1-ci0002\lib\net45\DiscUtils.Iso9660.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Lvm, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Lvm.0.15.1-ci0002\lib\net45\DiscUtils.Lvm.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Net, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Net.0.15.1-ci0002\lib\net45\DiscUtils.Net.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Nfs, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Nfs.0.15.1-ci0002\lib\net45\DiscUtils.Nfs.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Ntfs, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Ntfs.0.15.1-ci0002\lib\net45\DiscUtils.Ntfs.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.OpticalDiscSharing, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.OpticalDiscSharing.0.15.1-ci0002\lib\net45\DiscUtils.OpticalDiscSharing.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.OpticalDisk, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.OpticalDisk.0.15.1-ci0002\lib\net45\DiscUtils.OpticalDisk.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Registry, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Registry.0.15.1-ci0002\lib\net45\DiscUtils.Registry.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Sdi, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Sdi.0.15.1-ci0002\lib\net45\DiscUtils.Sdi.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.SquashFs, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.SquashFs.0.15.1-ci0002\lib\net45\DiscUtils.SquashFs.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Streams, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Streams.0.15.1-ci0002\lib\net45\DiscUtils.Streams.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Swap, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Swap.0.15.1-ci0002\lib\net45\DiscUtils.Swap.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Udf, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Udf.0.15.1-ci0002\lib\net45\DiscUtils.Udf.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Vdi, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Vdi.0.15.1-ci0002\lib\net45\DiscUtils.Vdi.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Vhd, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Vhd.0.15.1-ci0002\lib\net45\DiscUtils.Vhd.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Vhdx, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Vhdx.0.15.1-ci0002\lib\net45\DiscUtils.Vhdx.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Vmdk, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Vmdk.0.15.1-ci0002\lib\net45\DiscUtils.Vmdk.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Wim, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Wim.0.15.1-ci0002\lib\net45\DiscUtils.Wim.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Xfs, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Xfs.0.15.1-ci0002\lib\net45\DiscUtils.Xfs.dll</HintPath>
+    </Reference>
+    <Reference Include="DiscUtils.Xva, Version=0.15.1.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\DiscUtils.Xva.0.15.1-ci0002\lib\net45\DiscUtils.Xva.dll</HintPath>
+    </Reference>
+    <Reference Include="lzo.net, Version=0.0.3.0, Culture=neutral, PublicKeyToken=7c3b11b873e0cbca, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\lzo.net.0.0.3\lib\net45\lzo.net.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.ServiceProcess" />
@@ -53,16 +148,23 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="CliProgram.cs" />
+    <Compile Include="DiskUtils\FsMaker.cs" />
+    <Compile Include="SampleProgram.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="ServiceUtils\ServiceAccount.cs" />
     <Compile Include="ServiceUtils\ServiceHelper.cs" />
     <Compile Include="ServiceUtils\ServiceNotExistException.cs" />
     <Compile Include="ServiceUtils\ServiceStartType.cs" />
     <Compile Include="Storage\CapacityUnits.cs" />
-    <Compile Include="Storage\IStorage.cs" />
-    <Compile Include="Storage\RamStorage.cs" />
+    <Compile Include="Storage\Implements\RawImageStorage.cs" />
+    <Compile Include="Storage\Implements\SnapshotBddImageStorage.cs" />
+    <Compile Include="Storage\Implements\StandaloneBddImageStorage.cs" />
+    <Compile Include="Storage\ISvdStorage.cs" />
+    <Compile Include="Storage\Implements\RamStorage.cs" />
     <Compile Include="Storage\TrimDescriptor.cs" />
     <Compile Include="App\SvdService.cs" />
+    <Compile Include="Storage\NativeIoSupport.cs" />
+    <Compile Include="TasteProgram.cs" />
     <Compile Include="WindowsService\SvdServiceForWindows.cs">
       <SubType>Component</SubType>
     </Compile>
@@ -75,6 +177,7 @@
   <ItemGroup>
     <Content Include="ServiceUtils\SOURCE.txt" />
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
     <PostBuildEvent>if $(ConfigurationName) == Release setlocal enabledelayedexpansion enableextensions

+ 47 - 0
SvdCli/TasteProgram.cs

@@ -0,0 +1,47 @@
+using System;
+using DiscUtils;
+using DiscUtils.Ntfs;
+using DiscUtils.Partitions;
+using DiscUtils.Streams;
+using DiscUtils.Vhd;
+using System.IO;
+
+namespace SvdCli
+{
+    internal class TasteProgram
+    {
+        private static void Main(string[] args)
+        {
+            var diskSize = 5L * 1024 * 1024 * 1024;
+
+            using (var fs = new FileStream(@"z:\test.img", FileMode.OpenOrCreate))
+            {
+                {
+                    VirtualDisk destDisk = DiscUtils.Raw.Disk.Initialize(fs, Ownership.None, diskSize);
+                    BiosPartitionTable.Initialize(destDisk, WellKnownPartitionType.WindowsNtfs);
+                    var volMgr = new VolumeManager(destDisk);
+                    var label = "ZZZZZZ";
+                    using (var destNtfs =
+                        NtfsFileSystem.Format(volMgr.GetLogicalVolumes()[0], label, new NtfsFormatOptions()))
+                    {
+                        destNtfs.CreateDirectory("temp");
+                    }
+
+                    //commit everything to the stream before closing
+                    fs.Flush();
+                }
+            }
+
+            using (var fs = new FileStream(@"z:\test.img", FileMode.OpenOrCreate))
+            {
+                VirtualDisk destDisk = new DiscUtils.Raw.Disk(fs, Ownership.None);
+
+                var volMgr = new VolumeManager(destDisk);
+
+                using var sparseStream = volMgr.GetLogicalVolumes()[0].Open();
+                new NtfsFileSystemChecker(sparseStream).Check(Console.Out, ReportLevels.All);
+            }
+
+        }
+    }
+}

+ 30 - 0
SvdCli/packages.config

@@ -1,4 +1,34 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="DiscUtils" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.BootConfig" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Btrfs" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Core" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Dmg" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Ext" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Fat" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.HfsPlus" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Iscsi" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Iso9660" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Lvm" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Net" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Nfs" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Ntfs" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.OpticalDiscSharing" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.OpticalDisk" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Registry" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Sdi" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.SquashFs" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Streams" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Swap" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Udf" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Vdi" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Vhd" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Vhdx" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Vmdk" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Wim" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Xfs" version="0.15.1-ci0002" targetFramework="net461" />
+  <package id="DiscUtils.Xva" version="0.15.1-ci0002" targetFramework="net461" />
   <package id="ILRepack" version="2.0.18" targetFramework="net461" />
+  <package id="lzo.net" version="0.0.3" targetFramework="net461" />
 </packages>