├── .gitattributes ├── .gitignore ├── Austin.WindowsProjectedFileSystem ├── Austin.WindowsProjectedFileSystem.csproj ├── Compatibility.cs ├── FileBasicInfo.cs ├── IProjectedFileSystemCallbacks.cs ├── Interop │ ├── HResult.cs │ ├── Interop.cs │ ├── NtDll.cs │ └── ProjFs │ │ ├── PRJ_CALLBACKS.cs │ │ ├── PRJ_CALLBACK_DATA.cs │ │ ├── PRJ_CALLBACK_DATA_FLAGS.cs │ │ ├── PRJ_CANCEL_COMMAND_CB.cs │ │ ├── PRJ_END_DIRECTORY_ENUMERATION_CB.cs │ │ ├── PRJ_FILE_BASIC_INFO.cs │ │ ├── PRJ_GET_DIRECTORY_ENUMERATION_CB.cs │ │ ├── PRJ_GET_FILE_DATA_CB.cs │ │ ├── PRJ_GET_PLACEHOLDER_INFO_CB.cs │ │ ├── PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT.cs │ │ ├── PRJ_NOTIFICATION.cs │ │ ├── PRJ_NOTIFICATION_CB.cs │ │ ├── PRJ_NOTIFICATION_PARAMETERS.cs │ │ ├── PRJ_NOTIFY_TYPES.cs │ │ ├── PRJ_PLACEHOLDER_INFO.cs │ │ ├── PRJ_PLACEHOLDER_VERSION_INFO.cs │ │ ├── PRJ_QUERY_FILE_NAME_CB.cs │ │ ├── PRJ_START_DIRECTORY_ENUMERATION_CB.cs │ │ └── ProjFs.cs ├── ProjectedFileSystem.EnumStatus.cs └── ProjectedFileSystem.cs ├── README.markdown ├── ZFSOnDiskFormat.pdf ├── ZfsDokan ├── Dokan │ ├── DokanNet.cs │ ├── DokanOperations.cs │ └── Proxy.cs ├── Program.cs ├── ZfsDokan.cs └── ZfsDokan.csproj ├── ZfsProjFs ├── Program.cs ├── ProjectedItemInfo.cs └── ZfsProjFs.csproj ├── ZfsSharp.sln ├── ZfsSharp ├── Program.cs └── ZfsSharp.csproj └── ZfsSharpLib ├── - ├── Guard.cs ├── System │ ├── DataStructures │ │ ├── AvlTree.cs │ │ ├── AvlTreeNode.cs │ │ ├── BinaryTreeBase.cs │ │ ├── BinaryTreeNode.cs │ │ └── FuncComparer.cs │ └── IComparableExtensions.cs └── Throw.cs ├── CRC.cs ├── Checksum ├── Flecter4.cs ├── NoChecksum.cs └── Sha256.cs ├── Compression ├── GZip.cs ├── LZ4.cs ├── Lzjb.cs └── NoCompression.cs ├── DNode.cs ├── DataSetType.cs ├── DatasetDirectory.cs ├── Extensions.cs ├── HardDisk ├── FileHardDisk.cs ├── GptHardDrive.cs ├── HardDisk.cs ├── MbrHardDisk.cs ├── OffsetHardDisk.cs ├── OffsetTableHardDisk.cs ├── VdiHardDisk.cs ├── VhdHardDisk.cs └── VhdxHardDisk.cs ├── LeafVdevInfo.cs ├── MetaSlabs.cs ├── NvList.cs ├── NvListBinaryReader.cs ├── ObjectSet.cs ├── Program.cs ├── RangeMap.cs ├── Structs.Ddt.cs ├── Structs.Dmu.cs ├── Structs.Dsl.cs ├── Structs.SpaceMaps.cs ├── Structs.Zio.cs ├── Structs.Zpl.cs ├── VirtualDevices ├── HddVdev.cs ├── MirrorVdev.cs ├── RaidzVdev.cs └── Vdev.cs ├── Zap.cs ├── Zfs.cs ├── ZfsSharpLib.csproj ├── Zio.cs ├── Zpl.cs └── lz4net ├── LZ4.portable └── LZ4Codec.portable.cs ├── LZ4 ├── ILZ4Service.cs ├── LZ4Codec.cs ├── LZ4StreamFlags.cs ├── LZ4StreamMode.cs └── Services │ ├── Safe32LZ4Service.cs │ └── Safe64LZ4Service.cs └── LZ4ps ├── LZ4Codec.Safe.cs ├── LZ4Codec.Safe32.Dirty.cs ├── LZ4Codec.Safe32HC.Dirty.cs ├── LZ4Codec.Safe64.Dirty.cs ├── LZ4Codec.Safe64HC.Dirty.cs └── LZ4Codec.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *proj.user 3 | bin 4 | obj 5 | *.cache 6 | TestResults 7 | packages 8 | *.psess 9 | *.vsp 10 | .vs/ 11 | *.vspx 12 | launchSettings.json 13 | -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Austin.WindowsProjectedFileSystem.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | true 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Compatibility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace Austin.WindowsProjectedFileSystem 7 | { 8 | public enum CompatibilityStatus 9 | { 10 | UnsupportedPlatform, 11 | UnsupportedArchitecture, 12 | UnsupportedOsVersion, 13 | FeatureNotEnabled, 14 | Supported, 15 | } 16 | 17 | public static class Compatibility 18 | { 19 | public static CompatibilityStatus Status { get; } = getStatus(); 20 | 21 | static CompatibilityStatus getStatus() 22 | { 23 | if (Environment.OSVersion.Platform != PlatformID.Win32NT) 24 | { 25 | return CompatibilityStatus.UnsupportedPlatform; 26 | } 27 | 28 | if (!Environment.Is64BitProcess) 29 | { 30 | //It appears that ProjectedFSLib.dll only exists as a 64-bit DLL. 31 | return CompatibilityStatus.UnsupportedArchitecture; 32 | } 33 | 34 | //In .NET Core versions before 3, the host executable does not contain a manifest that specifies supportedOs, 35 | //so Environment.OSVersion.Version lies. So we have to call RtlGetVersion ourselves. 36 | //In .NET Core 3 a proper manfiest is included: 37 | //https://github.com/dotnet/core-setup/issues/5106 38 | var winVer = Interop.NtDll.RtlGetVersion(); 39 | if (winVer.Major < 10) 40 | { 41 | //TODO: do we need a tigher version check? 42 | return CompatibilityStatus.UnsupportedOsVersion; 43 | } 44 | 45 | 46 | //TODO: maybe use DISM API to figure out if the feature is enabled. 47 | if (!File.Exists(Path.Combine(Environment.SystemDirectory, Interop.ProjectedFfLibDll))) 48 | { 49 | return CompatibilityStatus.FeatureNotEnabled; 50 | } 51 | 52 | return CompatibilityStatus.Supported; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/FileBasicInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Austin.WindowsProjectedFileSystem 5 | { 6 | public class FileBasicInfo 7 | { 8 | public string Name { get; set; } 9 | public bool IsDirectory { get; set; } 10 | public long FileSize { get; set; } 11 | public DateTime CreationTime { get; set; } 12 | public DateTime LastAccessTime { get; set; } 13 | public DateTime LastWriteTime { get; set; } 14 | public DateTime ChangeTime { get; set; } 15 | public FileAttributes Attributes { get; set; } 16 | 17 | internal Interop.ProjFs.PRJ_FILE_BASIC_INFO GoNative() 18 | { 19 | var ret = new Interop.ProjFs.PRJ_FILE_BASIC_INFO() 20 | { 21 | FileSize = this.FileSize, 22 | IsDirectory = this.IsDirectory, 23 | FileAttributes = (uint)this.Attributes, 24 | }; 25 | if (this.ChangeTime.Year >= 1601) 26 | ret.ChangeTime = this.ChangeTime.ToFileTimeUtc(); 27 | if (this.CreationTime.Year >= 1601) 28 | ret.CreationTime = this.CreationTime.ToFileTimeUtc(); 29 | if (this.LastAccessTime.Year >= 1601) 30 | ret.LastAccessTime = this.LastAccessTime.ToFileTimeUtc(); 31 | if (this.LastWriteTime.Year >= 1601) 32 | ret.LastWriteTime = this.LastWriteTime.ToFileTimeUtc(); 33 | return ret; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/IProjectedFileSystemCallbacks.cs: -------------------------------------------------------------------------------- 1 | namespace Austin.WindowsProjectedFileSystem 2 | { 3 | public interface IProjectedFileSystemCallbacks 4 | { 5 | FileBasicInfo[] EnumerateDirectory(bool isWildCardExpression, string directory, string searchExpression); 6 | bool FileExists(string fileName); 7 | //TODO: add support for extended attributes, etc. 8 | FileBasicInfo QueryFileInfo(string fileName); 9 | bool GetFileData(string fileName, byte[] buf, ulong offset, uint length); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/HResult.cs: -------------------------------------------------------------------------------- 1 | namespace Austin.WindowsProjectedFileSystem 2 | { 3 | partial class Interop 4 | { 5 | public static class HResult 6 | { 7 | const int FACILITY_NT_BIT = 0x10000000; 8 | static int HRESULT_FROM_NT(uint status) 9 | { 10 | return (int)(status | FACILITY_NT_BIT); 11 | } 12 | 13 | const int FACILITY_WIN32 = 7; 14 | static int HRESULT_FROM_WIN32(uint x) 15 | { 16 | return (int)(x) <= 0 ? (int)(x) : (int)(((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000); 17 | } 18 | 19 | public static readonly int STATUS_CANNOT_DELETE = HRESULT_FROM_NT(0xC0000121); 20 | public static readonly int ERROR_FILE_NOT_FOUND = HRESULT_FROM_WIN32(2); 21 | public static readonly int ERROR_INVALID_PARAMETER = HRESULT_FROM_WIN32(87); 22 | public static readonly int ERROR_INSUFFICIENT_BUFFER = HRESULT_FROM_WIN32(122); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/Interop.cs: -------------------------------------------------------------------------------- 1 | namespace Austin.WindowsProjectedFileSystem 2 | { 3 | static partial class Interop 4 | { 5 | public const string ProjectedFfLibDll = "ProjectedFSLib.dll"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/NtDll.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Austin.WindowsProjectedFileSystem 5 | { 6 | partial class Interop 7 | { 8 | public static class NtDll 9 | { 10 | [StructLayout(LayoutKind.Sequential)] 11 | internal struct RTL_OSVERSIONINFOEX 12 | { 13 | internal uint dwOSVersionInfoSize; 14 | internal uint dwMajorVersion; 15 | internal uint dwMinorVersion; 16 | internal uint dwBuildNumber; 17 | internal uint dwPlatformId; 18 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 19 | internal string szCSDVersion; 20 | } 21 | 22 | [DllImport("ntdll")] 23 | private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation); 24 | 25 | internal static Version RtlGetVersion() 26 | { 27 | RTL_OSVERSIONINFOEX v = new RTL_OSVERSIONINFOEX(); 28 | v.dwOSVersionInfoSize = (uint)Marshal.SizeOf(v); 29 | if (RtlGetVersion(out v) == 0) 30 | { 31 | return new Version((int)v.dwMajorVersion, (int)v.dwMinorVersion, (int)v.dwBuildNumber, (int)v.dwPlatformId); 32 | } 33 | else 34 | { 35 | throw new Exception("RtlGetVersion failed!"); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_CALLBACKS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Austin.WindowsProjectedFileSystem 5 | { 6 | partial class Interop 7 | { 8 | partial class ProjFs 9 | { 10 | [StructLayout(LayoutKind.Sequential)] 11 | public class PRJ_CALLBACKS 12 | { 13 | public PRJ_START_DIRECTORY_ENUMERATION_CB StartDirectoryEnumerationCallback; 14 | public PRJ_END_DIRECTORY_ENUMERATION_CB EndDirectoryEnumerationCallback; 15 | public PRJ_GET_DIRECTORY_ENUMERATION_CB GetDirectoryEnumerationCallback; 16 | public PRJ_GET_PLACEHOLDER_INFO_CB GetPlaceholderInfoCallback; 17 | public PRJ_GET_FILE_DATA_CB GetFileDataCallback; 18 | 19 | public PRJ_QUERY_FILE_NAME_CB QueryFileNameCallback; 20 | public PRJ_NOTIFICATION_CB NotificationCallback; 21 | public PRJ_CANCEL_COMMAND_CB CancelCommandCallback; 22 | } 23 | [StructLayout(LayoutKind.Sequential)] 24 | public class PRJ_CALLBACKS_INTPTR 25 | { 26 | public IntPtr StartDirectoryEnumerationCallback; 27 | public IntPtr EndDirectoryEnumerationCallback; 28 | public IntPtr GetDirectoryEnumerationCallback; 29 | public IntPtr GetPlaceholderInfoCallback; 30 | public IntPtr GetFileDataCallback; 31 | 32 | public IntPtr QueryFileNameCallback; 33 | public IntPtr NotificationCallback; 34 | public IntPtr CancelCommandCallback; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_CALLBACK_DATA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace Austin.WindowsProjectedFileSystem 7 | { 8 | partial class Interop 9 | { 10 | partial class ProjFs 11 | { 12 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 13 | public struct PRJ_CALLBACK_DATA 14 | { 15 | public UInt32 Size; 16 | public PRJ_CALLBACK_DATA_FLAGS Flags; 17 | public IntPtr NamespaceVirtualizationContext; 18 | public Int32 CommandId; 19 | public Guid FileId; 20 | public Guid DataStreamId; 21 | public string FilePathName; 22 | IntPtr VersionInfo; 23 | public UInt32 TriggeringProcessId; 24 | public string TriggeringProcessImageFileName; 25 | IntPtr InstanceContext; 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_CALLBACK_DATA_FLAGS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | partial class ProjFs 8 | { 9 | [Flags] 10 | public enum PRJ_CALLBACK_DATA_FLAGS : UInt32 11 | { 12 | None = 0, 13 | RESTART_SCAN = 0x00000001, 14 | RETURN_SINGLE_ENTRY = 0x00000002, 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_CANCEL_COMMAND_CB.cs: -------------------------------------------------------------------------------- 1 | namespace Austin.WindowsProjectedFileSystem 2 | { 3 | partial class Interop 4 | { 5 | partial class ProjFs 6 | { 7 | public delegate void PRJ_CANCEL_COMMAND_CB(in PRJ_CALLBACK_DATA callbackData); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_END_DIRECTORY_ENUMERATION_CB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | partial class ProjFs 8 | { 9 | public delegate Int32 PRJ_END_DIRECTORY_ENUMERATION_CB(in PRJ_CALLBACK_DATA callbackData, in Guid enumerationId); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_FILE_BASIC_INFO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Austin.WindowsProjectedFileSystem 5 | { 6 | partial class Interop 7 | { 8 | partial class ProjFs 9 | { 10 | [StructLayout(LayoutKind.Sequential)] 11 | public struct PRJ_FILE_BASIC_INFO 12 | { 13 | [MarshalAs(UnmanagedType.U1)] 14 | public bool IsDirectory; 15 | public Int64 FileSize; 16 | public Int64 CreationTime; 17 | public Int64 LastAccessTime; 18 | public Int64 LastWriteTime; 19 | public Int64 ChangeTime; 20 | public UInt32 FileAttributes; 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_GET_DIRECTORY_ENUMERATION_CB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Austin.WindowsProjectedFileSystem 5 | { 6 | partial class Interop 7 | { 8 | partial class ProjFs 9 | { 10 | public delegate Int32 PRJ_GET_DIRECTORY_ENUMERATION_CB(in PRJ_CALLBACK_DATA callbackData, in Guid enumerationId, [MarshalAs(UnmanagedType.LPWStr)] string searchExpression, IntPtr dirEntryBufferHandle); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_GET_FILE_DATA_CB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | partial class ProjFs 8 | { 9 | public delegate Int32 PRJ_GET_FILE_DATA_CB(in PRJ_CALLBACK_DATA callbackData, UInt64 byteOffset, UInt32 length); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_GET_PLACEHOLDER_INFO_CB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | partial class ProjFs 8 | { 9 | public delegate Int32 PRJ_GET_PLACEHOLDER_INFO_CB(in PRJ_CALLBACK_DATA callbackData); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | partial class ProjFs 8 | { 9 | public class PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT : SafeHandleZeroOrMinusOneIsInvalid 10 | { 11 | public PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT() 12 | : base(true) 13 | { 14 | } 15 | 16 | protected override bool ReleaseHandle() 17 | { 18 | PrjStopVirtualizing(this.handle); 19 | return true; 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_NOTIFICATION.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | partial class ProjFs 8 | { 9 | public enum PRJ_NOTIFICATION : UInt32 10 | { 11 | FILE_OPENED = 0x00000002, 12 | NEW_FILE_CREATED = 0x00000004, 13 | FILE_OVERWRITTEN = 0x00000008, 14 | PRE_DELETE = 0x00000010, 15 | PRE_RENAME = 0x00000020, 16 | PRE_SET_HARDLINK = 0x00000040, 17 | FILE_RENAMED = 0x00000080, 18 | HARDLINK_CREATED = 0x00000100, 19 | FILE_HANDLE_CLOSED_NO_MODIFICATION = 0x00000200, 20 | FILE_HANDLE_CLOSED_FILE_MODIFIED = 0x00000400, 21 | FILE_HANDLE_CLOSED_FILE_DELETED = 0x00000800, 22 | FILE_PRE_CONVERT_TO_FULL = 0x00001000, 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_NOTIFICATION_CB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Austin.WindowsProjectedFileSystem 5 | { 6 | partial class Interop 7 | { 8 | partial class ProjFs 9 | { 10 | public delegate Int32 PRJ_NOTIFICATION_CB( 11 | in PRJ_CALLBACK_DATA callbackData, 12 | [MarshalAs(UnmanagedType.U1)] bool isDirectory, 13 | PRJ_NOTIFICATION notification, 14 | [MarshalAs(UnmanagedType.LPWStr)] string destinationFileName, 15 | ref PRJ_NOTIFICATION_PARAMETERS operationParameters); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_NOTIFICATION_PARAMETERS.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | partial class ProjFs 8 | { 9 | [StructLayout(LayoutKind.Explicit)] 10 | public struct PRJ_NOTIFICATION_PARAMETERS 11 | { 12 | [FieldOffset(0)] 13 | public PRJ_NOTIFY_TYPES PostCreate; 14 | [FieldOffset(0)] 15 | public PRJ_NOTIFY_TYPES FileRenamed; 16 | [FieldOffset(0), MarshalAs(UnmanagedType.U1)] 17 | public bool FileDeletedOnHandleClose; 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_NOTIFY_TYPES.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | partial class ProjFs 8 | { 9 | [Flags] 10 | public enum PRJ_NOTIFY_TYPES : UInt32 11 | { 12 | None = 0x00000000, 13 | SUPPRESS_NOTIFICATIONS = 0x00000001, 14 | FILE_OPENED = 0x00000002, 15 | NEW_FILE_CREATED = 0x00000004, 16 | FILE_OVERWRITTEN = 0x00000008, 17 | PRE_DELETE = 0x00000010, 18 | PRE_RENAME = 0x00000020, 19 | PRE_SET_HARDLINK = 0x00000040, 20 | FILE_RENAMED = 0x00000080, 21 | HARDLINK_CREATED = 0x00000100, 22 | FILE_HANDLE_CLOSED_NO_MODIFICATION = 0x00000200, 23 | FILE_HANDLE_CLOSED_FILE_MODIFIED = 0x00000400, 24 | FILE_HANDLE_CLOSED_FILE_DELETED = 0x00000800, 25 | FILE_PRE_CONVERT_TO_FULL = 0x00001000, 26 | USE_EXISTING_MASK = 0xFFFFFFFF 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_PLACEHOLDER_INFO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Austin.WindowsProjectedFileSystem 5 | { 6 | partial class Interop 7 | { 8 | partial class ProjFs 9 | { 10 | [StructLayout(LayoutKind.Sequential)] 11 | public unsafe struct PRJ_PLACEHOLDER_INFO 12 | { 13 | public PRJ_FILE_BASIC_INFO FileBasicInfo; 14 | public UInt32 EaBufferSize; 15 | public UInt32 OffsetToFirstEa; 16 | public UInt32 SecurityBufferSize; 17 | public UInt32 OffsetToSecurityDescriptor; 18 | public UInt32 StreamsInfoBufferSize; 19 | public UInt32 OffsetToFirstStreamInfo; 20 | public PRJ_PLACEHOLDER_VERSION_INFO VersionInfo; 21 | fixed byte VariableData[1]; 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_PLACEHOLDER_VERSION_INFO.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | public static partial class ProjFs 8 | { 9 | [StructLayout(LayoutKind.Sequential)] 10 | public unsafe struct PRJ_PLACEHOLDER_VERSION_INFO 11 | { 12 | public const int PRJ_PLACEHOLDER_ID_LENGTH = 128; 13 | fixed byte ProviderID[PRJ_PLACEHOLDER_ID_LENGTH]; 14 | fixed byte ContentID[PRJ_PLACEHOLDER_ID_LENGTH]; 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_QUERY_FILE_NAME_CB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | partial class ProjFs 8 | { 9 | public delegate Int32 PRJ_QUERY_FILE_NAME_CB(in PRJ_CALLBACK_DATA callbackData); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/PRJ_START_DIRECTORY_ENUMERATION_CB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Austin.WindowsProjectedFileSystem 4 | { 5 | partial class Interop 6 | { 7 | partial class ProjFs 8 | { 9 | public delegate Int32 PRJ_START_DIRECTORY_ENUMERATION_CB(in PRJ_CALLBACK_DATA callbackData, in Guid enumerationId); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/Interop/ProjFs/ProjFs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Austin.WindowsProjectedFileSystem 5 | { 6 | partial class Interop 7 | { 8 | public static partial class ProjFs 9 | { 10 | [DllImport(ProjectedFfLibDll, ExactSpelling = true, CharSet = CharSet.Unicode)] 11 | public static extern Int32 PrjMarkDirectoryAsPlaceholder(string rootPathName, string targetPathName, IntPtr versionInfo, in Guid virtualizationInstanceID); 12 | 13 | [DllImport(ProjectedFfLibDll, ExactSpelling = true, CharSet = CharSet.Unicode)] 14 | public static extern Int32 PrjStartVirtualizing(string virtualizationRootPath, IntPtr callbacks, IntPtr instanceContext, IntPtr options, out PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext); 15 | 16 | [DllImport(ProjectedFfLibDll, ExactSpelling = true, CharSet = CharSet.Unicode)] 17 | static extern Int32 PrjStopVirtualizing(IntPtr namespaceVirtualizationContext); 18 | 19 | [DllImport(ProjectedFfLibDll, ExactSpelling = true, CharSet = CharSet.Unicode)] 20 | [return: MarshalAs(UnmanagedType.U1)] 21 | public static extern bool PrjFileNameMatch(string fileNameToCheck, string pattern); 22 | 23 | [DllImport(ProjectedFfLibDll, ExactSpelling = true, CharSet = CharSet.Unicode)] 24 | public static extern Int32 PrjFillDirEntryBuffer(string fileName, in PRJ_FILE_BASIC_INFO fileBasicInfo, IntPtr dirEntryBufferHandle); 25 | 26 | [DllImport(ProjectedFfLibDll, ExactSpelling = true, CharSet = CharSet.Unicode)] 27 | [return: MarshalAs(UnmanagedType.U1)] 28 | public static extern bool PrjDoesNameContainWildCards(string fileName); 29 | 30 | [DllImport(ProjectedFfLibDll, ExactSpelling = true, CharSet = CharSet.Unicode)] 31 | public static extern Int32 PrjWritePlaceholderInfo(IntPtr namespaceVirtualizationContext, string destinationFileName, in PRJ_PLACEHOLDER_INFO placeholderInfo, UInt32 placeholderInfoSize); 32 | 33 | [DllImport(ProjectedFfLibDll, ExactSpelling = true, CharSet = CharSet.Unicode)] 34 | public static extern unsafe Int32 PrjWriteFileData(IntPtr namespaceVirtualizationContext, in Guid dataStreamId, byte* buffer, UInt64 byteOffset, UInt32 length); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Austin.WindowsProjectedFileSystem/ProjectedFileSystem.EnumStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Austin.WindowsProjectedFileSystem 6 | { 7 | partial class ProjectedFileSystem 8 | { 9 | class EnumerationStatus 10 | { 11 | public FileBasicInfo[] Entries; 12 | public int CurrentIndex; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | What is this? 2 | ============= 3 | 4 | This is a C# program that reads ZFS file systems. Writing is explicitly a non-goal. 5 | Several types of disk images are supported: raw, VHD, VHDX, and VDI. 6 | RAIDZ, mirror, and stripe vdevs are supported. 7 | 8 | Code Layout 9 | ----------- 10 | 11 | * ZfsSharpLib: A library for reading ZFS files. 12 | * ZfsProjFs: Mount a pool using [Windows Projected Filesystem][ProjFS]. 13 | * Austin.WindowsProjectedFileSystem: A library for using Windows Projected Filesystem. Not specific 14 | to ZFS. 15 | * ZfsDokan: Mount a pool using [Dokan]. 16 | * ZfsSharp: Mostly used for testing and benchmarking. 17 | 18 | Future plans 19 | ------------ 20 | 21 | * Improve Windows Project Filesystem support. Currently it works mostly ok, but for some reason 22 | directory entries are duplicated. 23 | * Add support for FUSE. 24 | * Add `async` support to parrelleize checksumming and decompression. 25 | 26 | What I'm learning 27 | ----------------- 28 | 29 | It was exciting to get enough code working so I could read the uberblock in disk label. However it 30 | was a long way from there to reading any information out of the pool as I had to implement 31 | decompression and checksumming. 32 | This made obvious how fundamentally data integrity is backed into ZFS. 33 | 34 | The second moment of zen was to see the bock pointer's abstraction of 128k blocks of copy-on-write 35 | data being used by dnodes to create an abstraction of arbitrarily large pieces of data. 36 | By merely implementing the dnode and blkptr abstractions you get access to the fundamental data management tools 37 | of ZFS. Everything else in ZFS is just reading out of those. 38 | 39 | I found it interesting that ZFS incorporates three different ways of storing key-value pairs: ZAP, XDR, and SA. 40 | Each system is designed for different performance profiles: 41 | 42 | * SA: compactly storing small amounts of data, where many objects use the same key 43 | * ZAP: fast lookup by key to handle large directories 44 | * XDR: Config data that is rarely read or written. Probably used because it was lying around in 45 | Solaris for NFS, so why create something new. 46 | 47 | When I implemented the third system I started to get a feeling of Déjà vu. 48 | 49 | What parts of ZFS I wish were better documented 50 | ----------------------------------------------- 51 | 52 | During the creation of this I found the [ZFS On-Disk Specification][ZfsSpec] to be quite helpful. 53 | However it describes the initial version of zpool and zfs. OpenZFS is forked off OpenSolaris at 54 | zpool version 28 and zfs 5. Since then OpenZFS has added many [features][ZfsFeatures], so there are 55 | several aspects of the system that are lacking diagrams in the On-Disk Specification document. 56 | Eventually I'd like to make a blog post full of pretty pictures to describe these structures. 57 | 58 | An example of outdated documentation in the On-Disk Specification is the `znode_phys_t` structure. 59 | In zfs v5 this was replaced with the system attribute (SA) system. The comment at the start of sa.c 60 | is pretty good so it was not difficult to figure out. I think a nice diagram would have made it 61 | even easier to understand. 62 | 63 | The description of the Fat ZAP makes sense now that I've implemented it. However I remember it 64 | being somewhat obtuse while implementing it. 65 | 66 | The best documentation on how XDR worked was looking at GRUB's code. Since it only needs to 67 | implement enough to boot the system, it quickly gets to the heart of the matter. 68 | So XDR could benefit from more documentation. 69 | 70 | [ZfsSpec]: ZFSOnDiskFormat.pdf 71 | [ProjFS]: https://docs.microsoft.com/en-us/windows/desktop/projfs/projected-file-system 72 | [Dokan]: https://dokan-dev.github.io/ 73 | [ZfsFeatures]: http://www.open-zfs.org/wiki/Feature_Flags 74 | -------------------------------------------------------------------------------- /ZFSOnDiskFormat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AustinWise/ZfsSharp/895b991b35d133b1b233755297eb39f2bdc4d4fa/ZFSOnDiskFormat.pdf -------------------------------------------------------------------------------- /ZfsDokan/Dokan/DokanNet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Dokan 7 | { 8 | public class DokanOptions 9 | { 10 | public ushort Version; 11 | public ushort ThreadCount; 12 | public bool DebugMode; 13 | public bool UseStdErr; 14 | public bool UseAltStream; 15 | public bool UseKeepAlive; 16 | public bool NetworkDrive; 17 | public bool RemovableDrive; 18 | public string VolumeLabel; 19 | public string MountPoint; 20 | } 21 | 22 | 23 | // this struct must be the same layout as DOKAN_OPERATIONS 24 | [StructLayout(LayoutKind.Sequential, Pack = 4)] 25 | struct DOKAN_OPERATIONS 26 | { 27 | public Proxy.CreateFileDelegate CreateFile; 28 | public Proxy.OpenDirectoryDelegate OpenDirectory; 29 | public Proxy.CreateDirectoryDelegate CreateDirectory; 30 | public Proxy.CleanupDelegate Cleanup; 31 | public Proxy.CloseFileDelegate CloseFile; 32 | public Proxy.ReadFileDelegate ReadFile; 33 | public Proxy.WriteFileDelegate WriteFile; 34 | public Proxy.FlushFileBuffersDelegate FlushFileBuffers; 35 | public Proxy.GetFileInformationDelegate GetFileInformation; 36 | public Proxy.FindFilesDelegate FindFiles; 37 | public IntPtr FindFilesWithPattern; // this is not used in DokanNet 38 | public Proxy.SetFileAttributesDelegate SetFileAttributes; 39 | public Proxy.SetFileTimeDelegate SetFileTime; 40 | public Proxy.DeleteFileDelegate DeleteFile; 41 | public Proxy.DeleteDirectoryDelegate DeleteDirectory; 42 | public Proxy.MoveFileDelegate MoveFile; 43 | public Proxy.SetEndOfFileDelegate SetEndOfFile; 44 | public Proxy.SetAllocationSizeDelegate SetAllocationSize; 45 | public Proxy.LockFileDelegate LockFile; 46 | public Proxy.UnlockFileDelegate UnlockFile; 47 | public Proxy.GetDiskFreeSpaceDelegate GetDiskFreeSpace; 48 | public Proxy.GetVolumeInformationDelegate GetVolumeInformation; 49 | public Proxy.UnmountDelegate Unmount; 50 | public Proxy.GetFileSecurityDelegate GetFileSecurity; 51 | public Proxy.SetFileSecurityDelegate SetFileSecurity; 52 | } 53 | 54 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)] 55 | struct DOKAN_OPTIONS 56 | { 57 | public ushort Version; 58 | public ushort ThreadCount; // number of threads to be used 59 | public uint Options; 60 | public ulong Dummy1; 61 | [MarshalAs(UnmanagedType.LPWStr)] 62 | public string MountPoint; 63 | } 64 | 65 | 66 | class Dokan 67 | { 68 | [DllImport("dokan.dll")] 69 | public static extern int DokanMain(ref DOKAN_OPTIONS options, ref DOKAN_OPERATIONS operations); 70 | 71 | [DllImport("dokan.dll")] 72 | public static extern int DokanUnmount(int driveLetter); 73 | 74 | [DllImport("dokan.dll")] 75 | public static extern int DokanRemoveMountPoint( 76 | [MarshalAs(UnmanagedType.LPWStr)] string mountPoint); 77 | 78 | [DllImport("dokan.dll")] 79 | public static extern uint DokanVersion(); 80 | 81 | [DllImport("dokan.dll")] 82 | public static extern uint DokanDriveVersion(); 83 | 84 | [DllImport("dokan.dll")] 85 | public static extern bool DokanResetTimeout(uint timeout, ref DOKAN_FILE_INFO rawFileInfo); 86 | } 87 | 88 | 89 | public class DokanNet 90 | { 91 | public const int ERROR_FILE_NOT_FOUND = 2; 92 | public const int ERROR_PATH_NOT_FOUND = 3; 93 | public const int ERROR_ACCESS_DENIED = 5; 94 | public const int ERROR_SHARING_VIOLATION = 32; 95 | public const int ERROR_INVALID_NAME = 123; 96 | public const int ERROR_FILE_EXISTS = 80; 97 | public const int ERROR_ALREADY_EXISTS = 183; 98 | 99 | public const int DOKAN_SUCCESS = 0; 100 | public const int DOKAN_ERROR = -1; // General Error 101 | public const int DOKAN_DRIVE_LETTER_ERROR = -2; // Bad Drive letter 102 | public const int DOKAN_DRIVER_INSTALL_ERROR = -3; // Can't install driver 103 | public const int DOKAN_START_ERROR = -4; // Driver something wrong 104 | public const int DOKAN_MOUNT_ERROR = -5; // Can't assign drive letter 105 | 106 | public const int DOKAN_VERSION = 600; // ver 0.6.0 107 | 108 | private const uint DOKAN_OPTION_DEBUG = 1; 109 | private const uint DOKAN_OPTION_STDERR = 2; 110 | private const uint DOKAN_OPTION_ALT_STREAM = 4; 111 | private const uint DOKAN_OPTION_KEEP_ALIVE = 8; 112 | private const uint DOKAN_OPTION_NETWORK = 16; 113 | private const uint DOKAN_OPTION_REMOVABLE = 32; 114 | 115 | public static int DokanMain(DokanOptions options, DokanOperations operations) 116 | { 117 | if (options.VolumeLabel == null) 118 | { 119 | options.VolumeLabel = "DOKAN"; 120 | } 121 | 122 | Proxy proxy = new Proxy(options, operations); 123 | 124 | DOKAN_OPTIONS dokanOptions = new DOKAN_OPTIONS(); 125 | 126 | dokanOptions.Version = options.Version; 127 | if (dokanOptions.Version == 0) 128 | { 129 | dokanOptions.Version = DOKAN_VERSION; 130 | } 131 | dokanOptions.ThreadCount = options.ThreadCount; 132 | dokanOptions.Options |= options.DebugMode ? DOKAN_OPTION_DEBUG : 0; 133 | dokanOptions.Options |= options.UseStdErr ? DOKAN_OPTION_STDERR : 0; 134 | dokanOptions.Options |= options.UseAltStream ? DOKAN_OPTION_ALT_STREAM : 0; 135 | dokanOptions.Options |= options.UseKeepAlive ? DOKAN_OPTION_KEEP_ALIVE : 0; 136 | dokanOptions.Options |= options.NetworkDrive ? DOKAN_OPTION_NETWORK : 0; 137 | dokanOptions.Options |= options.RemovableDrive ? DOKAN_OPTION_REMOVABLE : 0; 138 | dokanOptions.MountPoint = options.MountPoint; 139 | 140 | DOKAN_OPERATIONS dokanOperations = new DOKAN_OPERATIONS(); 141 | dokanOperations.CreateFile = proxy.CreateFileProxy; 142 | dokanOperations.OpenDirectory = proxy.OpenDirectoryProxy; 143 | dokanOperations.CreateDirectory = proxy.CreateDirectoryProxy; 144 | dokanOperations.Cleanup = proxy.CleanupProxy; 145 | dokanOperations.CloseFile = proxy.CloseFileProxy; 146 | dokanOperations.ReadFile = proxy.ReadFileProxy; 147 | dokanOperations.WriteFile = proxy.WriteFileProxy; 148 | dokanOperations.FlushFileBuffers = proxy.FlushFileBuffersProxy; 149 | dokanOperations.GetFileInformation = proxy.GetFileInformationProxy; 150 | dokanOperations.FindFiles = proxy.FindFilesProxy; 151 | dokanOperations.SetFileAttributes = proxy.SetFileAttributesProxy; 152 | dokanOperations.SetFileTime = proxy.SetFileTimeProxy; 153 | dokanOperations.DeleteFile = proxy.DeleteFileProxy; 154 | dokanOperations.DeleteDirectory = proxy.DeleteDirectoryProxy; 155 | dokanOperations.MoveFile = proxy.MoveFileProxy; 156 | dokanOperations.SetEndOfFile = proxy.SetEndOfFileProxy; 157 | dokanOperations.SetAllocationSize = proxy.SetAllocationSizeProxy; 158 | dokanOperations.LockFile = proxy.LockFileProxy; 159 | dokanOperations.UnlockFile = proxy.UnlockFileProxy; 160 | dokanOperations.GetDiskFreeSpace = proxy.GetDiskFreeSpaceProxy; 161 | dokanOperations.GetVolumeInformation = proxy.GetVolumeInformationProxy; 162 | dokanOperations.Unmount = proxy.UnmountProxy; 163 | 164 | return Dokan.DokanMain(ref dokanOptions, ref dokanOperations); 165 | } 166 | 167 | 168 | public static int DokanUnmount(char driveLetter) 169 | { 170 | return Dokan.DokanUnmount(driveLetter); 171 | } 172 | 173 | public static int DokanRemoveMountPoint(string mountPoint) 174 | { 175 | return Dokan.DokanRemoveMountPoint(mountPoint); 176 | } 177 | 178 | public static uint DokanVersion() 179 | { 180 | return Dokan.DokanVersion(); 181 | } 182 | 183 | public static uint DokanDriverVersion() 184 | { 185 | return Dokan.DokanDriveVersion(); 186 | } 187 | 188 | public static bool DokanResetTimeout(uint timeout, DokanFileInfo fileinfo) 189 | { 190 | DOKAN_FILE_INFO rawFileInfo = new DOKAN_FILE_INFO(); 191 | rawFileInfo.DokanContext = fileinfo.DokanContext; 192 | return Dokan.DokanResetTimeout(timeout, ref rawFileInfo); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /ZfsDokan/Dokan/DokanOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections; 4 | 5 | namespace Dokan 6 | { 7 | public class DokanFileInfo 8 | { 9 | public Object Context; 10 | public bool IsDirectory; 11 | public ulong InfoId; 12 | public uint ProcessId; 13 | public bool DeleteOnClose; 14 | public bool PagingIo; 15 | public bool SynchronousIo; 16 | public bool Nocache; 17 | public bool WriteToEndOfFile; 18 | public readonly ulong DokanContext; // for internal use 19 | 20 | public DokanFileInfo(ulong dokanContext) 21 | { 22 | Context = null; 23 | IsDirectory = false; 24 | DeleteOnClose = false; 25 | PagingIo = false; 26 | SynchronousIo = false; 27 | Nocache = false; 28 | WriteToEndOfFile = false; 29 | InfoId = 0; 30 | DokanContext = dokanContext; 31 | } 32 | } 33 | 34 | 35 | public class FileInformation 36 | { 37 | public FileAttributes Attributes; 38 | public DateTime CreationTime; 39 | public DateTime LastAccessTime; 40 | public DateTime LastWriteTime; 41 | public long Length; 42 | public string FileName; 43 | /* 44 | public FileInformation() 45 | { 46 | Attributes = FileAttributes.Normal; 47 | Length = 0; 48 | } 49 | */ 50 | } 51 | 52 | public interface DokanOperations 53 | { 54 | int CreateFile( 55 | string filename, 56 | FileAccess access, 57 | FileShare share, 58 | FileMode mode, 59 | FileOptions options, 60 | DokanFileInfo info); 61 | 62 | int OpenDirectory( 63 | string filename, 64 | DokanFileInfo info); 65 | 66 | int CreateDirectory( 67 | string filename, 68 | DokanFileInfo info); 69 | 70 | int Cleanup( 71 | string filename, 72 | DokanFileInfo info); 73 | 74 | int CloseFile( 75 | string filename, 76 | DokanFileInfo info); 77 | 78 | int ReadFile( 79 | string filename, 80 | byte[] buffer, 81 | ref uint readBytes, 82 | long offset, 83 | DokanFileInfo info); 84 | 85 | int WriteFile( 86 | string filename, 87 | byte[] buffer, 88 | ref uint writtenBytes, 89 | long offset, 90 | DokanFileInfo info); 91 | 92 | int FlushFileBuffers( 93 | string filename, 94 | DokanFileInfo info); 95 | 96 | int GetFileInformation( 97 | string filename, 98 | FileInformation fileinfo, 99 | DokanFileInfo info); 100 | 101 | int FindFiles( 102 | string filename, 103 | ArrayList files, 104 | DokanFileInfo info); 105 | 106 | int SetFileAttributes( 107 | string filename, 108 | FileAttributes attr, 109 | DokanFileInfo info); 110 | 111 | int SetFileTime( 112 | string filename, 113 | DateTime ctime, 114 | DateTime atime, 115 | DateTime mtime, 116 | DokanFileInfo info); 117 | 118 | int DeleteFile( 119 | string filename, 120 | DokanFileInfo info); 121 | 122 | int DeleteDirectory( 123 | string filename, 124 | DokanFileInfo info); 125 | 126 | int MoveFile( 127 | string filename, 128 | string newname, 129 | bool replace, 130 | DokanFileInfo info); 131 | 132 | int SetEndOfFile( 133 | string filename, 134 | long length, 135 | DokanFileInfo info); 136 | 137 | int SetAllocationSize( 138 | string filename, 139 | long length, 140 | DokanFileInfo info); 141 | 142 | int LockFile( 143 | string filename, 144 | long offset, 145 | long length, 146 | DokanFileInfo info); 147 | 148 | int UnlockFile( 149 | string filename, 150 | long offset, 151 | long length, 152 | DokanFileInfo info); 153 | 154 | int GetDiskFreeSpace( 155 | ref ulong freeBytesAvailable, 156 | ref ulong totalBytes, 157 | ref ulong totalFreeBytes, 158 | DokanFileInfo info); 159 | 160 | int Unmount( 161 | DokanFileInfo info); 162 | 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /ZfsDokan/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Dokan; 6 | using ZfsSharpLib; 7 | 8 | namespace ZfsDokan 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | //args = new string[] { @"C:\VPC\SmartOs\" }; 15 | if (args.Length == 0) 16 | { 17 | Console.WriteLine("Usage: ZfsSharp.exe "); 18 | return; 19 | } 20 | 21 | using (var zfs = new Zfs(args[0])) 22 | { 23 | DokanOptions opt = new DokanOptions(); 24 | opt.MountPoint = "z:\\"; 25 | opt.DebugMode = true; 26 | opt.UseStdErr = true; 27 | opt.VolumeLabel = "ZFS"; 28 | int status = DokanNet.DokanMain(opt, new ZfsDokan(zfs)); 29 | switch (status) 30 | { 31 | case DokanNet.DOKAN_DRIVE_LETTER_ERROR: 32 | Console.WriteLine("Drvie letter error"); 33 | break; 34 | case DokanNet.DOKAN_DRIVER_INSTALL_ERROR: 35 | Console.WriteLine("Driver install error"); 36 | break; 37 | case DokanNet.DOKAN_MOUNT_ERROR: 38 | Console.WriteLine("Mount error"); 39 | break; 40 | case DokanNet.DOKAN_START_ERROR: 41 | Console.WriteLine("Start error"); 42 | break; 43 | case DokanNet.DOKAN_ERROR: 44 | Console.WriteLine("Unknown error"); 45 | break; 46 | case DokanNet.DOKAN_SUCCESS: 47 | Console.WriteLine("Success"); 48 | break; 49 | default: 50 | Console.WriteLine("Unknown status: {0}", status); 51 | break; 52 | 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ZfsDokan/ZfsDokan.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Dokan; 6 | using System.Collections; 7 | using System.IO; 8 | using ZfsSharpLib; 9 | 10 | namespace ZfsDokan 11 | { 12 | class ZfsDokan : DokanOperations 13 | { 14 | private Dictionary mItems = new Dictionary(StringComparer.OrdinalIgnoreCase); 15 | private Dictionary mRevItems = new Dictionary(); 16 | 17 | public ZfsDokan(Zfs zfs) 18 | { 19 | foreach (var ds in zfs.GetAllDataSets().Where(ds => ds.Type == DataSetType.ZFS && ds.Name.Contains("var"))) 20 | { 21 | 22 | loadItems(ds.GetHeadZfs().Root); 23 | 24 | break; 25 | } 26 | } 27 | 28 | void loadItems(Zpl.ZfsItem root) 29 | { 30 | var path = root.FullPath.Replace('/', '\\'); 31 | if (mItems.ContainsKey(path)) 32 | return; 33 | 34 | if (!FilterItem(root)) 35 | return; 36 | 37 | mItems.Add(path, root); 38 | mRevItems.Add(root, path); 39 | 40 | if (root is Zpl.ZfsDirectory) 41 | { 42 | foreach (var f in ((Zpl.ZfsDirectory)root).GetChildren()) 43 | { 44 | loadItems(f); 45 | } 46 | } 47 | } 48 | 49 | static bool FilterItem(Zpl.ZfsItem item) 50 | { 51 | return item is Zpl.ZfsDirectory || (item is Zpl.ZfsFile && ((Zpl.ZfsFile)item).Type == ZfsItemType.S_IFREG); 52 | } 53 | 54 | public int CreateFile(string filename, FileAccess access, FileShare share, FileMode mode, FileOptions options, DokanFileInfo info) 55 | { 56 | if (!mItems.ContainsKey(filename)) 57 | return -1; 58 | return 0; 59 | } 60 | 61 | public int OpenDirectory(string filename, DokanFileInfo info) 62 | { 63 | return 0; 64 | } 65 | 66 | public int CreateDirectory(string filename, DokanFileInfo info) 67 | { 68 | return -1; 69 | } 70 | 71 | public int Cleanup(string filename, DokanFileInfo info) 72 | { 73 | return 0; 74 | } 75 | 76 | public int CloseFile(string filename, DokanFileInfo info) 77 | { 78 | return 0; 79 | } 80 | 81 | public int ReadFile(string filename, byte[] buffer, ref uint readBytes, long offset, DokanFileInfo info) 82 | { 83 | //TODO 84 | if (!mItems.ContainsKey(filename)) 85 | return -1; 86 | 87 | var item = mItems[filename] as Zpl.ZfsFile; 88 | if (item == null) 89 | return -1; 90 | 91 | if (offset >= item.Length || buffer.Length == 0) 92 | return -1; 93 | 94 | int bytesToRead = buffer.Length; 95 | if (offset + bytesToRead > item.Length) 96 | { 97 | bytesToRead = (int)(item.Length - offset); 98 | } 99 | 100 | item.GetContents(new Span(buffer, 0, bytesToRead), offset); 101 | readBytes = (uint)bytesToRead; 102 | return 0; 103 | } 104 | 105 | public int WriteFile(string filename, byte[] buffer, ref uint writtenBytes, long offset, DokanFileInfo info) 106 | { 107 | return -1; 108 | } 109 | 110 | public int FlushFileBuffers(string filename, DokanFileInfo info) 111 | { 112 | return -1; 113 | } 114 | 115 | public int GetFileInformation(string filename, FileInformation fileinfo, DokanFileInfo info) 116 | { 117 | if (!mItems.ContainsKey(filename)) 118 | return -1; 119 | 120 | var item = mItems[filename]; 121 | 122 | SetAttributes(item, fileinfo); 123 | 124 | return 0; 125 | } 126 | 127 | private static void SetAttributes(Zpl.ZfsItem item, FileInformation finfo) 128 | { 129 | if (item is Zpl.ZfsDirectory) 130 | finfo.Attributes = FileAttributes.Directory; 131 | else if (item is Zpl.ZfsFile) 132 | finfo.Attributes = FileAttributes.Normal; 133 | else 134 | throw new NotSupportedException(); 135 | 136 | finfo.LastAccessTime = item.ATIME; 137 | finfo.LastWriteTime = item.MTIME; 138 | finfo.CreationTime = item.CTIME; 139 | if (item is Zpl.ZfsFile) 140 | { 141 | var zplFile = (Zpl.ZfsFile)item; 142 | finfo.Length = zplFile.Length; 143 | } 144 | } 145 | 146 | public int FindFiles(string filename, ArrayList files, DokanFileInfo info) 147 | { 148 | if (!mItems.ContainsKey(filename)) 149 | return -1; 150 | 151 | var dir = mItems[filename] as Zpl.ZfsDirectory; 152 | if (dir == null) 153 | return -1; 154 | 155 | foreach (var item in dir.GetChildren().Where(FilterItem)) 156 | { 157 | FileInformation finfo = new FileInformation(); 158 | finfo.FileName = item.Name; 159 | SetAttributes(item, finfo); 160 | files.Add(finfo); 161 | } 162 | 163 | return 0; 164 | } 165 | 166 | public int SetFileAttributes(string filename, FileAttributes attr, DokanFileInfo info) 167 | { 168 | return -1; 169 | } 170 | 171 | public int SetFileTime(string filename, DateTime ctime, DateTime atime, DateTime mtime, DokanFileInfo info) 172 | { 173 | return -1; 174 | } 175 | 176 | public int DeleteFile(string filename, DokanFileInfo info) 177 | { 178 | return -1; 179 | } 180 | 181 | public int DeleteDirectory(string filename, DokanFileInfo info) 182 | { 183 | return -1; 184 | } 185 | 186 | public int MoveFile(string filename, string newname, bool replace, DokanFileInfo info) 187 | { 188 | return -1; 189 | } 190 | 191 | public int SetEndOfFile(string filename, long length, DokanFileInfo info) 192 | { 193 | return -1; 194 | } 195 | 196 | public int SetAllocationSize(string filename, long length, DokanFileInfo info) 197 | { 198 | return -1; 199 | } 200 | 201 | public int LockFile(string filename, long offset, long length, DokanFileInfo info) 202 | { 203 | return 0; 204 | } 205 | 206 | public int UnlockFile(string filename, long offset, long length, DokanFileInfo info) 207 | { 208 | return 0; 209 | } 210 | 211 | public int GetDiskFreeSpace(ref ulong freeBytesAvailable, ref ulong totalBytes, ref ulong totalFreeBytes, DokanFileInfo info) 212 | { 213 | freeBytesAvailable = 512 * 1024 * 1024; 214 | totalBytes = 1024 * 1024 * 1024; 215 | totalFreeBytes = 512 * 1024 * 1024; 216 | return 0; 217 | } 218 | 219 | public int Unmount(DokanFileInfo info) 220 | { 221 | return 0; 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /ZfsDokan/ZfsDokan.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ZfsProjFs/Program.cs: -------------------------------------------------------------------------------- 1 | using Austin.WindowsProjectedFileSystem; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using ZfsSharpLib; 6 | 7 | namespace ZfsProjFs 8 | { 9 | class Program : IProjectedFileSystemCallbacks 10 | { 11 | Zfs mZfs; 12 | ProjectedFileSystem mProjFs; 13 | Dictionary mCache = new Dictionary(); 14 | 15 | static void Usage() 16 | { 17 | Console.WriteLine("Usage: ZfsProjFs.exe "); 18 | Console.WriteLine(" zfs disk image directory: A directory containing VHD, VHDX, VDI, or ZFS files."); 19 | Console.WriteLine(" target directory: Wherte to mount the ZFS file system."); 20 | } 21 | 22 | static int Main(string[] args) 23 | { 24 | try 25 | { 26 | return new Program().Run(args); 27 | } 28 | catch (Exception ex) 29 | { 30 | Console.WriteLine("Program crashed horribly:"); 31 | Console.WriteLine(ex); 32 | return 1; 33 | } 34 | } 35 | 36 | int Run(string[] args) 37 | { 38 | if (args.Length < 2) 39 | { 40 | Usage(); 41 | return 1; 42 | } 43 | 44 | string zfsDiskDir = args[0]; 45 | string targetDir = args[1]; 46 | 47 | if (!(Directory.Exists(zfsDiskDir) || File.Exists(zfsDiskDir)) || !Directory.Exists(targetDir)) 48 | { 49 | Console.WriteLine("Both directories must already exist."); 50 | Console.WriteLine(); 51 | Usage(); 52 | return 1; 53 | } 54 | 55 | if (Compatibility.Status != CompatibilityStatus.Supported) 56 | { 57 | Console.WriteLine("ProjectedFS is not supported: " + Compatibility.Status); 58 | return 1; 59 | } 60 | 61 | using (mZfs = new Zfs(zfsDiskDir)) 62 | { 63 | populateDatasets("", mZfs.GetRootDataset()); 64 | using (mProjFs = new ProjectedFileSystem(targetDir, this)) 65 | { 66 | Console.WriteLine("Start virtualizing in " + targetDir); 67 | Console.WriteLine("Press enter to exit."); 68 | Console.ReadLine(); 69 | } 70 | } 71 | 72 | return 0; 73 | } 74 | 75 | ProjectedItemInfo populateDatasets(string basePath, DatasetDirectory dsd) 76 | { 77 | var rootDir = dsd.GetHeadZfs().Root; 78 | var info = new ProjectedItemInfo() 79 | { 80 | ZfsItem = rootDir, 81 | ProjectedForm = new FileBasicInfo() 82 | { 83 | IsDirectory = true, 84 | Name = dsd.Name, 85 | }, 86 | FullName = basePath, 87 | }; 88 | mCache.Add(info.FullName, info); 89 | 90 | var childItems = getChildren(info.FullName, rootDir); 91 | 92 | var childDatasets = dsd.GetChildren(); 93 | foreach (var childDs in childDatasets) 94 | { 95 | if (childDs.Value.Type != DataSetType.ZFS) 96 | continue; 97 | childItems.Add(populateDatasets(basePath == "" ? childDs.Key : basePath + "\\" + childDs.Key, childDs.Value)); 98 | } 99 | info.Children = childItems.ToArray(); 100 | 101 | return info; 102 | } 103 | 104 | List getChildren(string baseName, Zpl.ZfsDirectory dir) 105 | { 106 | var projectedChildren = new List(); 107 | foreach (var c in dir.GetChildren()) 108 | { 109 | var type = c.Type; 110 | if (type != ZfsItemType.S_IFREG && type != ZfsItemType.S_IFDIR) 111 | continue; 112 | 113 | var childInfo = new ProjectedItemInfo(); 114 | childInfo.FullName = baseName == "" ? c.Name : baseName + "\\" + c.Name; 115 | childInfo.ProjectedForm = new FileBasicInfo() 116 | { 117 | Name = c.Name, 118 | IsDirectory = type == ZfsItemType.S_IFDIR, 119 | FileSize = type == ZfsItemType.S_IFREG ? ((Zpl.ZfsFile)c).Length : 0, 120 | Attributes = FileAttributes.ReadOnly, 121 | ChangeTime = c.MTIME, 122 | CreationTime = c.CTIME, 123 | LastAccessTime = c.ATIME, 124 | LastWriteTime = c.MTIME, 125 | }; 126 | childInfo.ZfsItem = c; 127 | projectedChildren.Add(childInfo); 128 | lock (mCache) 129 | { 130 | mCache[childInfo.FullName] = childInfo; 131 | } 132 | } 133 | return projectedChildren; 134 | } 135 | 136 | public FileBasicInfo[] EnumerateDirectory(bool isWildCardExpression, string directory, string searchExpression) 137 | { 138 | var ret = new List(); 139 | 140 | if (isWildCardExpression) 141 | { 142 | //TODO 143 | } 144 | else 145 | { 146 | ProjectedItemInfo info; 147 | lock (mCache) 148 | { 149 | mCache.TryGetValue(directory, out info); 150 | } 151 | if (info != null) 152 | { 153 | if (info.Children == null) 154 | { 155 | if (info.ZfsItem is Zpl.ZfsDirectory dir) 156 | { 157 | info.Children = getChildren(info.FullName, dir).ToArray(); 158 | } 159 | else 160 | { 161 | info.Children = new ProjectedItemInfo[0]; 162 | } 163 | } 164 | 165 | foreach (var c in info.Children) 166 | { 167 | if (mProjFs.FileNameMatch(c.FullName, searchExpression)) 168 | ret.Add(c.ProjectedForm); 169 | } 170 | } 171 | } 172 | return ret.ToArray(); 173 | } 174 | 175 | public bool FileExists(string fileName) 176 | { 177 | lock (mCache) 178 | { 179 | return mCache.ContainsKey(fileName); 180 | } 181 | } 182 | 183 | public FileBasicInfo QueryFileInfo(string fileName) 184 | { 185 | ProjectedItemInfo ret; 186 | lock (mCache) 187 | { 188 | mCache.TryGetValue(fileName, out ret); 189 | } 190 | return ret?.ProjectedForm; 191 | } 192 | 193 | public bool GetFileData(string fileName, byte[] buf, ulong offset, uint length) 194 | { 195 | ProjectedItemInfo ret; 196 | lock (mCache) 197 | { 198 | mCache.TryGetValue(fileName, out ret); 199 | } 200 | var file = ret?.ZfsItem as Zpl.ZfsFile; 201 | if (file == null) 202 | return false; 203 | checked 204 | { 205 | file.GetContents(new Span(buf, 0, (int)length), (long)offset); 206 | } 207 | return true; 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /ZfsProjFs/ProjectedItemInfo.cs: -------------------------------------------------------------------------------- 1 | using Austin.WindowsProjectedFileSystem; 2 | using ZfsSharpLib; 3 | 4 | namespace ZfsProjFs 5 | { 6 | class ProjectedItemInfo 7 | { 8 | public string FullName; 9 | public FileBasicInfo ProjectedForm; 10 | public Zpl.ZfsItem ZfsItem; 11 | public ProjectedItemInfo[] Children; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ZfsProjFs/ZfsProjFs.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ZfsSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2042 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZfsSharpLib", "ZfsSharpLib\ZfsSharpLib.csproj", "{6A0F1BE1-B663-4A9C-B57F-40602A5B221A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZfsSharp", "ZfsSharp\ZfsSharp.csproj", "{2927DC5D-C5E3-4556-875C-C54BD77E052F}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZfsDokan", "ZfsDokan\ZfsDokan.csproj", "{DE824396-0149-4CC2-B5DF-E496EEFACE97}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZfsProjFs", "ZfsProjFs\ZfsProjFs.csproj", "{D517BADE-DA8C-4215-8486-40A994345E25}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Austin.WindowsProjectedFileSystem", "Austin.WindowsProjectedFileSystem\Austin.WindowsProjectedFileSystem.csproj", "{42E809F5-D3E8-43B7-BD0E-BA49FD73AD38}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|x64 = Debug|x64 20 | Release|Any CPU = Release|Any CPU 21 | Release|x64 = Release|x64 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {6A0F1BE1-B663-4A9C-B57F-40602A5B221A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {6A0F1BE1-B663-4A9C-B57F-40602A5B221A}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {6A0F1BE1-B663-4A9C-B57F-40602A5B221A}.Debug|x64.ActiveCfg = Debug|Any CPU 27 | {6A0F1BE1-B663-4A9C-B57F-40602A5B221A}.Debug|x64.Build.0 = Debug|Any CPU 28 | {6A0F1BE1-B663-4A9C-B57F-40602A5B221A}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {6A0F1BE1-B663-4A9C-B57F-40602A5B221A}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {6A0F1BE1-B663-4A9C-B57F-40602A5B221A}.Release|x64.ActiveCfg = Release|Any CPU 31 | {6A0F1BE1-B663-4A9C-B57F-40602A5B221A}.Release|x64.Build.0 = Release|Any CPU 32 | {2927DC5D-C5E3-4556-875C-C54BD77E052F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {2927DC5D-C5E3-4556-875C-C54BD77E052F}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {2927DC5D-C5E3-4556-875C-C54BD77E052F}.Debug|x64.ActiveCfg = Debug|Any CPU 35 | {2927DC5D-C5E3-4556-875C-C54BD77E052F}.Debug|x64.Build.0 = Debug|Any CPU 36 | {2927DC5D-C5E3-4556-875C-C54BD77E052F}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {2927DC5D-C5E3-4556-875C-C54BD77E052F}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {2927DC5D-C5E3-4556-875C-C54BD77E052F}.Release|x64.ActiveCfg = Release|Any CPU 39 | {2927DC5D-C5E3-4556-875C-C54BD77E052F}.Release|x64.Build.0 = Release|Any CPU 40 | {DE824396-0149-4CC2-B5DF-E496EEFACE97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {DE824396-0149-4CC2-B5DF-E496EEFACE97}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {DE824396-0149-4CC2-B5DF-E496EEFACE97}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {DE824396-0149-4CC2-B5DF-E496EEFACE97}.Debug|x64.Build.0 = Debug|Any CPU 44 | {DE824396-0149-4CC2-B5DF-E496EEFACE97}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {DE824396-0149-4CC2-B5DF-E496EEFACE97}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {DE824396-0149-4CC2-B5DF-E496EEFACE97}.Release|x64.ActiveCfg = Release|Any CPU 47 | {DE824396-0149-4CC2-B5DF-E496EEFACE97}.Release|x64.Build.0 = Release|Any CPU 48 | {D517BADE-DA8C-4215-8486-40A994345E25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {D517BADE-DA8C-4215-8486-40A994345E25}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {D517BADE-DA8C-4215-8486-40A994345E25}.Debug|x64.ActiveCfg = Debug|Any CPU 51 | {D517BADE-DA8C-4215-8486-40A994345E25}.Debug|x64.Build.0 = Debug|Any CPU 52 | {D517BADE-DA8C-4215-8486-40A994345E25}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {D517BADE-DA8C-4215-8486-40A994345E25}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {D517BADE-DA8C-4215-8486-40A994345E25}.Release|x64.ActiveCfg = Release|Any CPU 55 | {D517BADE-DA8C-4215-8486-40A994345E25}.Release|x64.Build.0 = Release|Any CPU 56 | {42E809F5-D3E8-43B7-BD0E-BA49FD73AD38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {42E809F5-D3E8-43B7-BD0E-BA49FD73AD38}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {42E809F5-D3E8-43B7-BD0E-BA49FD73AD38}.Debug|x64.ActiveCfg = Debug|Any CPU 59 | {42E809F5-D3E8-43B7-BD0E-BA49FD73AD38}.Debug|x64.Build.0 = Debug|Any CPU 60 | {42E809F5-D3E8-43B7-BD0E-BA49FD73AD38}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {42E809F5-D3E8-43B7-BD0E-BA49FD73AD38}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {42E809F5-D3E8-43B7-BD0E-BA49FD73AD38}.Release|x64.ActiveCfg = Release|Any CPU 63 | {42E809F5-D3E8-43B7-BD0E-BA49FD73AD38}.Release|x64.Build.0 = Release|Any CPU 64 | EndGlobalSection 65 | GlobalSection(SolutionProperties) = preSolution 66 | HideSolutionNode = FALSE 67 | EndGlobalSection 68 | GlobalSection(ExtensibilityGlobals) = postSolution 69 | SolutionGuid = {D6D77C51-8677-402C-A63B-F7E96FFA9BD3} 70 | EndGlobalSection 71 | GlobalSection(Performance) = preSolution 72 | HasPerformanceSessions = true 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /ZfsSharp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using ZfsSharpLib; 8 | 9 | namespace ZfsSharp 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | if (args.Length == 0) 16 | { 17 | Console.WriteLine("Usage: ZfsSharp.exe "); 18 | return; 19 | } 20 | 21 | var sw = Stopwatch.StartNew(); 22 | sw.Stop(); 23 | 24 | using (var zfs = new Zfs(args[0])) 25 | { 26 | sw = Stopwatch.StartNew(); 27 | foreach (var ds in zfs.GetAllDataSets()) 28 | { 29 | Console.WriteLine("{0}: {1}", ds.Type, ds.Name); 30 | 31 | if (ds.Type != DataSetType.ZFS) 32 | continue; 33 | 34 | var zpl = ds.GetHeadZfs(); 35 | printContent(ds.Name, zpl.Root); 36 | 37 | if (ds.Name == "zones/var") 38 | Console.WriteLine(Encoding.ASCII.GetString(zpl.GetFileContents(@"/svc/log/svc.startd.log"))); 39 | 40 | foreach (var snap in ds.GetZfsSnapShots()) 41 | { 42 | var snapName = ds.Name + "@" + snap.Key; 43 | Console.WriteLine(snapName); 44 | printContent(snapName, snap.Value.Root); 45 | } 46 | } 47 | sw.Stop(); 48 | Console.WriteLine("time: " + sw.ElapsedMilliseconds); 49 | } 50 | 51 | Console.WriteLine(); 52 | } 53 | 54 | private static void DumpContents(string outPath, Zpl.ZfsItem item) 55 | { 56 | Console.WriteLine(item.FullPath); 57 | var dir = item as Zpl.ZfsDirectory; 58 | var file = item as Zpl.ZfsFile; 59 | 60 | var dest = Path.Combine(outPath, item.FullPath.Substring(1)); 61 | 62 | if (file != null) 63 | { 64 | File.WriteAllBytes(dest, file.GetContents()); 65 | } 66 | 67 | if (dir == null) 68 | return; 69 | if (!Directory.Exists(dest)) 70 | Directory.CreateDirectory(dest); 71 | foreach (var d in dir.GetChildren()) 72 | { 73 | DumpContents(outPath, d); 74 | } 75 | } 76 | 77 | private static void BenchmarkFileReading(Zfs zfs) 78 | { 79 | var varzpl = zfs.GetAllDataSets().Where(k => k.Name == "zones/var").Select(ds => ds.GetHeadZfs()).Single(); 80 | Stopwatch st = Stopwatch.StartNew(); 81 | for (int i = 0; i < 1000; i++) 82 | { 83 | varzpl.GetFileContents(@"/svc/log/svc.startd.log"); 84 | } 85 | st.Stop(); 86 | 87 | Console.WriteLine(st.Elapsed.TotalSeconds); 88 | } 89 | 90 | static void printContent(string namePrefix, Zpl.ZfsItem item) 91 | { 92 | //Console.WriteLine(namePrefix + item.FullPath); 93 | var dir = item as Zpl.ZfsDirectory; 94 | var file = item as Zpl.ZfsFile; 95 | 96 | if (file != null) 97 | { 98 | int length = (int)file.Length; 99 | byte[] bytes = ArrayPool.Shared.Rent(length); 100 | file.GetContents(new Span(bytes, 0, length), 0); 101 | ArrayPool.Shared.Return(bytes); 102 | } 103 | 104 | if (dir == null) 105 | return; 106 | foreach (var d in dir.GetChildren()) 107 | { 108 | printContent(namePrefix, d); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /ZfsSharp/ZfsSharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ZfsSharpLib/-/System/DataStructures/AvlTreeNode.cs: -------------------------------------------------------------------------------- 1 | namespace System.DataStructures 2 | { 3 | /// 4 | /// Node used by . 5 | /// 6 | /// Type of the data stored in the node. 7 | internal class AvlTreeNode : IBinaryTreeNode, T> 8 | { 9 | /// 10 | /// Initializes a new instance of the class. 11 | /// 12 | /// Value of node 13 | public AvlTreeNode(T value) 14 | { 15 | Value = value; 16 | Height = 1; 17 | } 18 | 19 | /// 20 | /// Gets the height of the node. 21 | /// 22 | public int Height { get; internal set; } 23 | 24 | /// 25 | /// Gets or sets the left node reference. 26 | /// 27 | public AvlTreeNode Left { get; set; } 28 | 29 | /// 30 | /// Gets or sets the right node reference. 31 | /// 32 | public AvlTreeNode Right { get; set; } 33 | 34 | /// 35 | /// Gets or sets the value of the node. 36 | /// 37 | public T Value { get; set; } 38 | } 39 | } -------------------------------------------------------------------------------- /ZfsSharpLib/-/System/DataStructures/BinaryTreeNode.cs: -------------------------------------------------------------------------------- 1 | namespace System.DataStructures 2 | { 3 | /// 4 | /// Interface for the nodes that are used in a . 5 | /// 6 | /// Type of the node. 7 | /// Type of the value. 8 | /// 9 | internal interface IBinaryTreeNode 10 | { 11 | /// 12 | /// Gets or sets the left node reference. 13 | /// 14 | /// The left. 15 | /// 16 | TNode Left { get; set; } 17 | 18 | /// 19 | /// Gets or sets the right node reference. 20 | /// 21 | /// The right. 22 | /// 23 | TNode Right { get; set; } 24 | 25 | /// 26 | /// Gets or sets the value of the . 27 | /// 28 | /// The value. 29 | /// 30 | TValue Value { get; set; } 31 | } 32 | 33 | /// 34 | /// Node used by a binary tree. 35 | /// 36 | /// Type of the node. 37 | /// 38 | internal class BinaryTreeNode : IBinaryTreeNode, T> 39 | { 40 | /// 41 | /// Initializes a new instance of the class. 42 | /// 43 | /// The value. 44 | /// 45 | public BinaryTreeNode(T value) { 46 | Value = value; 47 | } 48 | 49 | /// 50 | /// Gets or sets the left node reference. 51 | /// 52 | /// The left. 53 | /// 54 | public BinaryTreeNode Left { get; set; } 55 | 56 | /// 57 | /// Gets or sets the right node reference. 58 | /// 59 | /// The right. 60 | /// 61 | public BinaryTreeNode Right { get; set; } 62 | 63 | /// 64 | /// Gets or sets the value of the node. 65 | /// 66 | /// The value. 67 | /// 68 | public T Value { get; set; } 69 | } 70 | } -------------------------------------------------------------------------------- /ZfsSharpLib/-/System/DataStructures/FuncComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace System.DataStructures 4 | { 5 | /// 6 | /// Compares various functions of two objects 7 | /// 8 | /// The type of function comparer 9 | internal class FuncComparer : IComparer, IEqualityComparer 10 | { 11 | private readonly Func _compare; 12 | 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The comparer. 17 | public FuncComparer(Func comparer) 18 | { 19 | _compare = comparer; 20 | } 21 | 22 | /// 23 | /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. 24 | /// 25 | /// The first object to compare. 26 | /// The second object to compare. 27 | /// 28 | /// Value Condition Less than zero is less than .Zero equals .Greater than zero is greater than . 29 | /// 30 | public int Compare(T x, T y) 31 | { 32 | return _compare(x, y); 33 | } 34 | 35 | /// 36 | /// Determines whether the specified objects are equal. 37 | /// 38 | /// The first object of type T to compare. 39 | /// The second object of type T to compare. 40 | /// 41 | /// true if the specified objects are equal; otherwise, false. 42 | /// 43 | public bool Equals(T x, T y) 44 | { 45 | return (_compare(x, y) == 0); 46 | } 47 | 48 | /// 49 | /// Returns a hash code for the specified object. 50 | /// 51 | /// The for which a hash code is to be returned. 52 | /// A hash code for the specified object. 53 | /// The type of is a reference type and is null. 54 | public int GetHashCode(T obj) 55 | { 56 | return obj.GetHashCode(); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /ZfsSharpLib/-/Throw.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | 5 | internal static class Throw 6 | { 7 | [DebuggerStepThrough] 8 | internal static void ArgumentEmptyException(string paramName) 9 | { 10 | throw new ArgumentException(string.Format("{0} cannot be empty.", paramName), paramName); 11 | } 12 | 13 | [DebuggerStepThrough] 14 | internal static void ArgumentNullException(string paramName) 15 | { 16 | throw new ArgumentNullException(paramName, string.Format("{0} cannot be null.", paramName)); 17 | } 18 | 19 | [DebuggerStepThrough] 20 | internal static void Exception(string message) 21 | { 22 | throw new Exception(message); 23 | } 24 | 25 | [DebuggerStepThrough] 26 | internal static void FileNotFoundException(string filename) 27 | { 28 | string message = string.Format("File [{0}] was not found.", filename); 29 | #if !CompactFramework && !SILVERLIGHT 30 | throw new FileNotFoundException(message, filename); 31 | #else 32 | throw new FileNotFoundException(message); 33 | #endif 34 | } 35 | 36 | [DebuggerStepThrough] 37 | internal static void GuardAgainstFailure(string message) 38 | { 39 | throw new Exception(string.Format("Guard Against: {0}", message)); 40 | } 41 | 42 | [DebuggerStepThrough] 43 | internal static void GuardAssertFailure(string message) 44 | { 45 | throw new Exception(string.Format("Guard Assert: {0}", message)); 46 | } 47 | 48 | [DebuggerStepThrough] 49 | internal static void GuardFailureNotGreaterThan(string name, T value, T minimum) 50 | { 51 | string message = string.Format("Guard Greater: {0} is '{1}' but must be greater than '{2}'.", name, value, minimum); 52 | throw new Exception(message); 53 | } 54 | 55 | [DebuggerStepThrough] 56 | internal static void GuardFailureNotGreaterThanOrEqualTo(string name, T value, T minimum) 57 | { 58 | string message = string.Format("Guard Greater or Equal: {0} is '{1}' but must be greater than or equal to '{2}'.", 59 | name, 60 | value, 61 | minimum); 62 | throw new Exception(message); 63 | } 64 | 65 | [DebuggerStepThrough] 66 | internal static void GuardFailureNotLessThan(string name, T value, T maximum) 67 | { 68 | string message = string.Format("Guard Less: {0} is '{1}' but must be less than '{2}'.", name, value, maximum); 69 | throw new Exception(message); 70 | } 71 | 72 | [DebuggerStepThrough] 73 | internal static void GuardFailureNotLessThanOrEqualTo(string name, T value, T maximum) 74 | { 75 | string message = string.Format("Guard Less or Equal: {0} is '{1}' but must be less than or equal to '{2}'.", 76 | name, 77 | value, 78 | maximum); 79 | throw new Exception(message); 80 | } 81 | 82 | [DebuggerStepThrough] 83 | internal static void GuardTypeAssignmentFailure(Type typeToAssign, Type targetType, string name) 84 | { 85 | string message = string.Format("Guard Type Assignment: {0} is '{1}' but does not {2} '{3}'.", 86 | name, 87 | typeToAssign, 88 | targetType.IsInterface ? "implement required interface" : "convert to required type", 89 | targetType); 90 | throw new Exception(message); 91 | } 92 | } -------------------------------------------------------------------------------- /ZfsSharpLib/CRC.cs: -------------------------------------------------------------------------------- 1 | /* This is .NET safe implementation of Crc32C algorithm. 2 | * This implementation was found fastest from some variants, based on Robert Vazan native implementations 3 | * Also, it is good for x64 and for x86, so, it seems, there is no sense to do 2 different realizations. 4 | * Reference speed: Hardware: 20GB/s, Software Native: 2GB/s, this: 1GB/s 5 | * 6 | * Max Vysokikh, 2016 7 | */ 8 | 9 | namespace Crc32C 10 | { 11 | static class CRC 12 | { 13 | private const uint Poly = 0x82f63b78; 14 | 15 | private static readonly uint[] _table = new uint[16 * 256]; 16 | 17 | static CRC() 18 | { 19 | uint[] table = _table; 20 | for (uint i = 0; i < 256; i++) 21 | { 22 | uint res = i; 23 | for (int t = 0; t < 16; t++) 24 | { 25 | for (int k = 0; k < 8; k++) res = (res & 1) == 1 ? Poly ^ (res >> 1) : (res >> 1); 26 | table[(t * 256) + i] = res; 27 | } 28 | } 29 | } 30 | 31 | public static uint Compute(byte[] bytes) 32 | { 33 | return Append(0, bytes, 0, bytes.Length); 34 | } 35 | 36 | static uint Append(uint crc, byte[] input, int offset, int length) 37 | { 38 | uint crcLocal = uint.MaxValue ^ crc; 39 | 40 | uint[] table = _table; 41 | while (length >= 16) 42 | { 43 | crcLocal = table[(15 * 256) + ((crcLocal ^ input[offset]) & 0xff)] 44 | ^ table[(14 * 256) + (((crcLocal >> 8) ^ input[offset + 1]) & 0xff)] 45 | ^ table[(13 * 256) + (((crcLocal >> 16) ^ input[offset + 2]) & 0xff)] 46 | ^ table[(12 * 256) + (((crcLocal >> 24) ^ input[offset + 3]) & 0xff)] 47 | ^ table[(11 * 256) + input[offset + 4]] 48 | ^ table[(10 * 256) + input[offset + 5]] 49 | ^ table[(9 * 256) + input[offset + 6]] 50 | ^ table[(8 * 256) + input[offset + 7]] 51 | ^ table[(7 * 256) + input[offset + 8]] 52 | ^ table[(6 * 256) + input[offset + 9]] 53 | ^ table[(5 * 256) + input[offset + 10]] 54 | ^ table[(4 * 256) + input[offset + 11]] 55 | ^ table[(3 * 256) + input[offset + 12]] 56 | ^ table[(2 * 256) + input[offset + 13]] 57 | ^ table[(1 * 256) + input[offset + 14]] 58 | ^ table[(0 * 256) + input[offset + 15]]; 59 | offset += 16; 60 | length -= 16; 61 | } 62 | 63 | while (--length >= 0) 64 | crcLocal = table[(crcLocal ^ input[offset++]) & 0xff] ^ crcLocal >> 8; 65 | return crcLocal ^ uint.MaxValue; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ZfsSharpLib/Checksum/Flecter4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace ZfsSharpLib 5 | { 6 | class Flecter4 : IChecksum 7 | { 8 | public unsafe zio_cksum_t Calculate(ArraySegment input) 9 | { 10 | if (input.Count % 4 != 0) 11 | throw new ArgumentException("Input must have a length that is a multiple of 4."); 12 | 13 | ulong a, b, c, d; 14 | a = b = c = d = 0; 15 | fixed (byte* ptr = input.Array) 16 | { 17 | int size = input.Count / 4; 18 | uint* intPtr = (uint*)(ptr + input.Offset); 19 | for (int i = 0; i < size; i++) 20 | { 21 | a += intPtr[i]; 22 | b += a; 23 | c += b; 24 | d += c; 25 | } 26 | } 27 | 28 | zio_cksum_t ret = new zio_cksum_t() 29 | { 30 | word1 = a, 31 | word2 = b, 32 | word3 = c, 33 | word4 = d 34 | }; 35 | return ret; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ZfsSharpLib/Checksum/NoChecksum.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ZfsSharpLib 4 | { 5 | class NoChecksum : IChecksum 6 | { 7 | public zio_cksum_t Calculate(ArraySegment input) 8 | { 9 | return new zio_cksum_t(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ZfsSharpLib/Checksum/Sha256.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace ZfsSharpLib 5 | { 6 | class Sha256 : IChecksum 7 | { 8 | public zio_cksum_t Calculate(ArraySegment input) 9 | { 10 | byte[] checksumBytes; 11 | using (var sha = SHA256.Create()) 12 | { 13 | checksumBytes = sha.ComputeHash(input.Array, input.Offset, input.Count); 14 | } 15 | return Program.ToStructByteSwap(checksumBytes); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ZfsSharpLib/Compression/GZip.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ZfsSharpLib 4 | { 5 | class GZip : ICompression 6 | { 7 | public void Decompress(Span input, Span output) 8 | { 9 | //GZip is not very common, 10 | //so I'm dropping support for it as part of the Span<> upgrade. 11 | throw new NotImplementedException(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ZfsSharpLib/Compression/LZ4.cs: -------------------------------------------------------------------------------- 1 | using LZ4ps; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace ZfsSharpLib 8 | { 9 | class LZ4 : ICompression 10 | { 11 | public void Decompress(Span input, Span output) 12 | { 13 | var bufsiz = input[0] << 24 | input[1] << 16 | input[2] << 8 | input[3]; 14 | if (bufsiz + 4 > input.Length || bufsiz < 0) 15 | throw new ArgumentOutOfRangeException("Not enough bytes in input."); 16 | int ret = LZ4Codec.Decode64(input, 4, bufsiz, output, 0, output.Length, true); 17 | if (ret != output.Length) 18 | throw new Exception("Did not decompress the right number of bytes."); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ZfsSharpLib/Compression/Lzjb.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ZfsSharpLib 4 | { 5 | class Lzjb : ICompression 6 | { 7 | const int NBBY = 8; 8 | 9 | const int MATCH_BITS = 6; 10 | const int MATCH_MIN = 3; 11 | const int MATCH_MAX = ((1 << MATCH_BITS) + (MATCH_MIN - 1)); 12 | const int OFFSET_MASK = ((1 << (16 - MATCH_BITS)) - 1); 13 | const int LEMPEL_SIZE = 1024; 14 | 15 | public unsafe void Decompress(Span input, Span output) 16 | { 17 | fixed (byte* s_start = input) 18 | { 19 | fixed (byte* d_start = output) 20 | { 21 | int ret = lzjb_decompress(s_start, d_start, input.Length, output.Length, 0); 22 | if (ret != 0) 23 | throw new Exception($"LZJB failed to decompress: {ret}"); 24 | } 25 | } 26 | } 27 | unsafe int lzjb_decompress(byte* s_start, byte* d_start, long s_len, long d_len, int n) 28 | { 29 | byte* src = s_start; 30 | byte* dst = d_start; 31 | byte* d_end = d_start + d_len; 32 | byte* cpy; 33 | byte copymap = 0; 34 | int copymask = 1 << (NBBY - 1); 35 | 36 | while (dst < d_end) 37 | { 38 | if ((copymask <<= 1) == (1 << NBBY)) 39 | { 40 | copymask = 1; 41 | copymap = *src++; 42 | } 43 | if ((int)(copymap & copymask) != 0) 44 | { 45 | int mlen = (src[0] >> (NBBY - MATCH_BITS)) + MATCH_MIN; 46 | int offset = ((src[0] << NBBY) | src[1]) & OFFSET_MASK; 47 | src += 2; 48 | if ((cpy = dst - offset) < d_start) 49 | return (-1); 50 | while (--mlen >= 0 && dst < d_end) 51 | *dst++ = *cpy++; 52 | } 53 | else 54 | { 55 | *dst++ = *src++; 56 | } 57 | } 58 | return (0); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ZfsSharpLib/Compression/NoCompression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ZfsSharpLib 4 | { 5 | class NoCompression : ICompression 6 | { 7 | public void Decompress(Span input, Span output) 8 | { 9 | input.CopyTo(output); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ZfsSharpLib/DNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace ZfsSharpLib 5 | { 6 | class DNode 7 | { 8 | readonly Zio mZio; 9 | readonly Func mGetBlockKey; 10 | readonly Program.BlockReader mReadBlock; 11 | dnode_phys_t mPhys; 12 | 13 | public DNode(Zio zio, dnode_phys_t phys) 14 | { 15 | if (phys.NLevels == 0) 16 | throw new ArgumentOutOfRangeException(nameof(phys), "Expect dnode's NLevels to be > 0"); 17 | 18 | mZio = zio; 19 | mGetBlockKey = getBlockKey; 20 | mReadBlock = readBlock; 21 | mPhys = phys; 22 | } 23 | 24 | public dmu_object_type_t Type 25 | { 26 | get { return mPhys.Type; } 27 | } 28 | 29 | public dmu_object_type_t BonusType 30 | { 31 | get { return mPhys.BonusType; } 32 | } 33 | 34 | public bool IsNewType 35 | { 36 | get { return mPhys.IsNewType; } 37 | } 38 | 39 | public dmu_object_byteswap NewType 40 | { 41 | get { return mPhys.NewType; } 42 | } 43 | 44 | public int BlockSizeInBytes 45 | { 46 | get { return mPhys.BlockSizeInBytes; } 47 | } 48 | 49 | /// 50 | /// Maximum amount of data that can be read from this DNode. 51 | /// 52 | public long AvailableDataSize 53 | { 54 | get { return mPhys.AvailableDataSize; } 55 | } 56 | 57 | public DnodeFlags Flags 58 | { 59 | get { return mPhys.Flags; } 60 | } 61 | 62 | public dmu_object_type_t SpillType 63 | { 64 | get { return (mPhys.Flags & DnodeFlags.SpillBlkptr) == 0 ? dmu_object_type_t.NONE : mPhys.Spill.Type; } 65 | } 66 | 67 | public int SpillSize 68 | { 69 | get 70 | { 71 | if ((mPhys.Flags & DnodeFlags.SpillBlkptr) == 0) 72 | { 73 | throw new NotSupportedException("DNode does not have a spill block pointer."); 74 | } 75 | return mPhys.Spill.LogicalSizeBytes; 76 | } 77 | } 78 | 79 | unsafe void CalculateBonusSize(out int bonusOffset, out int maxBonusSize) 80 | { 81 | if (mPhys.BonusType == dmu_object_type_t.NONE) 82 | throw new Exception("No bonus type."); 83 | 84 | bonusOffset = (mPhys.NBlkPtrs - 1) * sizeof(blkptr_t); 85 | maxBonusSize = dnode_phys_t.DN_MAX_BONUSLEN - bonusOffset; 86 | if ((mPhys.Flags & DnodeFlags.SpillBlkptr) != 0) 87 | { 88 | maxBonusSize -= sizeof(blkptr_t); 89 | } 90 | } 91 | 92 | unsafe public T GetBonus() where T : struct 93 | { 94 | Type t = typeof(T); 95 | int structSize = Program.SizeOf(); 96 | int bonusOffset; 97 | int maxBonusSize; 98 | CalculateBonusSize(out bonusOffset, out maxBonusSize); 99 | 100 | if (structSize > maxBonusSize) 101 | throw new ArgumentOutOfRangeException(); 102 | if (structSize > mPhys.BonusLen) 103 | throw new ArgumentOutOfRangeException(); 104 | 105 | fixed (byte* pBonus = mPhys.Bonus) 106 | { 107 | return Program.ToStruct(pBonus, bonusOffset, maxBonusSize); 108 | } 109 | } 110 | 111 | public void ReadSpill(Span dest) 112 | { 113 | if ((mPhys.Flags & DnodeFlags.SpillBlkptr) == 0) 114 | { 115 | throw new NotSupportedException("DNode does not have a spill block pointer."); 116 | } 117 | 118 | var spill = mPhys.Spill; 119 | if (spill.fill != 1) 120 | { 121 | throw new NotImplementedException("Only spill pointers with fill = 1 supported."); 122 | } 123 | 124 | mZio.Read(spill, dest); 125 | } 126 | 127 | /// 128 | /// 129 | /// 130 | /// A buffer that should be return to 131 | unsafe public ArraySegment RentBonus() 132 | { 133 | int bonusOffset; 134 | int maxBonusSize; 135 | CalculateBonusSize(out bonusOffset, out maxBonusSize); 136 | 137 | if (mPhys.BonusLen > maxBonusSize) 138 | throw new Exception("Specified bonus size is larger than the dnode can hold."); 139 | 140 | var bonus = Program.RentBytes(mPhys.BonusLen); 141 | fixed (byte* pBonus = mPhys.Bonus) 142 | { 143 | Marshal.Copy(new IntPtr(pBonus + bonusOffset), bonus.Array, bonus.Offset, bonus.Count); 144 | } 145 | return bonus; 146 | } 147 | 148 | public byte[] Read(long offset, int size) 149 | { 150 | var ret = new byte[size]; 151 | Read(ret, offset, size); 152 | return ret; 153 | } 154 | 155 | public void Read(byte[] buffer, long offset, int size) 156 | { 157 | Read(new Span(buffer, 0, size), offset); 158 | } 159 | 160 | public void Read(ArraySegment dest, long offset) 161 | { 162 | Read((Span)dest, offset); 163 | } 164 | 165 | public void Read(Span dest, long offset) 166 | { 167 | if (offset < 0 || dest.Length < 0) 168 | throw new ArgumentOutOfRangeException(); 169 | if ((offset + dest.Length) > mPhys.AvailableDataSize) 170 | throw new ArgumentOutOfRangeException(); 171 | 172 | Program.MultiBlockCopy(dest, offset, mPhys.BlockSizeInBytes, mGetBlockKey, mReadBlock); 173 | } 174 | 175 | private void readBlock(Span dest, blkptr_t blkptr, int startNdx) 176 | { 177 | if (blkptr.IsHole) 178 | return; 179 | int logicalBlockSize = blkptr.LogicalSizeBytes; 180 | if (logicalBlockSize == dest.Length) 181 | { 182 | mZio.Read(blkptr, dest); 183 | } 184 | else 185 | { 186 | var src = Program.RentBytes(logicalBlockSize); 187 | mZio.Read(blkptr, src); 188 | new Span(src.Array, src.Offset + startNdx, dest.Length).CopyTo(dest); 189 | Program.ReturnBytes(src); 190 | } 191 | } 192 | 193 | private blkptr_t getBlockKey(long blockId) 194 | { 195 | int indirBlockShift = mPhys.IndirectBlockShift - blkptr_t.SPA_BLKPTRSHIFT; 196 | int indirMask = (1 << indirBlockShift) - 1; 197 | int indirSize = 1 << mPhys.IndirectBlockShift; 198 | 199 | blkptr_t ptr = default(blkptr_t); 200 | 201 | for (int i = 0; i < mPhys.NLevels; i++) 202 | { 203 | int indirectNdx = (int)(blockId >> ((mPhys.NLevels - i - 1) * indirBlockShift)) & indirMask; 204 | if (i == 0) 205 | { 206 | ptr = mPhys.GetBlkptr(indirectNdx); 207 | } 208 | else 209 | { 210 | var indirBlockRent = Program.RentBytes(indirSize); 211 | Span indirBlock = indirBlockRent; 212 | mZio.Read(ptr, indirBlock); 213 | const int BP_SIZE = 1 << blkptr_t.SPA_BLKPTRSHIFT; 214 | ptr = Program.ToStruct(indirBlock.Slice(indirectNdx * BP_SIZE, BP_SIZE)); 215 | Program.ReturnBytes(indirBlockRent); 216 | } 217 | 218 | if (ptr.IsHole) 219 | break; 220 | } 221 | 222 | return ptr; 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /ZfsSharpLib/DataSetType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ZfsSharpLib 7 | { 8 | public enum DataSetType 9 | { 10 | MetaData, 11 | ZFS, 12 | ZVOL, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ZfsSharpLib/DatasetDirectory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace ZfsSharpLib 6 | { 7 | public class DatasetDirectory 8 | { 9 | private ObjectSet mMos; 10 | private Zio mZio; 11 | private dsl_dir_phys_t mDslDir; 12 | private Dictionary mSnapShots = new Dictionary(); 13 | 14 | internal DatasetDirectory(ObjectSet mos, long objectid, string name, Zio zio) 15 | { 16 | this.mMos = mos; 17 | this.mZio = zio; 18 | this.Name = name; 19 | this.Type = DataSetType.MetaData; 20 | 21 | var rootDslObj = mos.ReadEntry(objectid); 22 | if (rootDslObj.Type != dmu_object_type_t.DSL_DIR) 23 | throw new NotSupportedException("Expected DSL_DIR dnode."); 24 | if (rootDslObj.BonusType != dmu_object_type_t.DSL_DIR) 25 | throw new NotSupportedException("Expected DSL_DIR bonus."); 26 | mDslDir = rootDslObj.GetBonus(); 27 | var rootDslProps = Zap.Parse(mos, mDslDir.props_zapobj); 28 | 29 | Dictionary clones; 30 | if (mDslDir.clones != 0) 31 | { 32 | clones = Zap.GetDirectoryEntries(mos, mDslDir.clones); 33 | } 34 | 35 | if (mDslDir.head_dataset_obj == 0) 36 | return; //probably meta data, like $MOS or $FREE 37 | var rootDataSetObj = mos.ReadEntry(mDslDir.head_dataset_obj); 38 | if (!IsDataSet(rootDataSetObj)) 39 | throw new Exception("Not a dataset!"); 40 | if (rootDataSetObj.BonusType != dmu_object_type_t.DSL_DATASET) 41 | throw new Exception("Missing dataset bonus!"); 42 | var headDs = rootDataSetObj.GetBonus(); 43 | 44 | if (headDs.bp.IsHole && mDslDir.origin_obj == 0) 45 | return; //this is $ORIGIN 46 | 47 | if (headDs.snapnames_zapobj != 0) 48 | { 49 | mSnapShots = Zap.GetDirectoryEntries(mMos, headDs.snapnames_zapobj); 50 | } 51 | 52 | if (headDs.bp.Type != dmu_object_type_t.OBJSET) 53 | throw new Exception("Expected OBJSET."); 54 | var headDsObjset = zio.Get(headDs.bp); 55 | switch (headDsObjset.Type) 56 | { 57 | case dmu_objset_type_t.DMU_OST_ZFS: 58 | this.Type = DataSetType.ZFS; 59 | break; 60 | case dmu_objset_type_t.DMU_OST_ZVOL: 61 | this.Type = DataSetType.ZVOL; 62 | break; 63 | default: 64 | throw new Exception("Unknow dataset type: " + headDsObjset.Type.ToString()); 65 | } 66 | } 67 | 68 | public DataSetType Type { get; private set; } 69 | public string Name { get; private set; } 70 | 71 | public Zpl GetHeadZfs() 72 | { 73 | return GetZfs(mDslDir.head_dataset_obj); 74 | } 75 | 76 | private Zpl GetZfs(long objectid) 77 | { 78 | return new Zpl(mMos, objectid, mZio); 79 | } 80 | 81 | public IEnumerable> GetZfsSnapShots() 82 | { 83 | return mSnapShots.Select(snap => new KeyValuePair(snap.Key, GetZfs(snap.Value))); 84 | } 85 | 86 | internal IEnumerable> GetChildIds() 87 | { 88 | if (mDslDir.child_dir_zapobj == 0) 89 | return Enumerable.Empty>(); 90 | return Zap.GetDirectoryEntries(mMos, mDslDir.child_dir_zapobj); 91 | } 92 | 93 | public Dictionary GetChildren() 94 | { 95 | var ret = new Dictionary(); 96 | if (mDslDir.child_dir_zapobj != 0) 97 | { 98 | foreach (var child in Zap.GetDirectoryEntries(mMos, mDslDir.child_dir_zapobj)) 99 | { 100 | ret.Add(child.Key, new DatasetDirectory(mMos, child.Value, child.Key, mZio)); 101 | } 102 | } 103 | return ret; 104 | } 105 | 106 | internal static bool IsDataSet(DNode dn) 107 | { 108 | return dn.Type == dmu_object_type_t.DSL_DATASET || (dn.IsNewType && dn.NewType == dmu_object_byteswap.DMU_BSWAP_ZAP); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ZfsSharpLib/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | 7 | namespace ZfsSharpLib 8 | { 9 | static class Extensions 10 | { 11 | public static ArraySegment SubSegment(this ArraySegment parent, int offset, int count) 12 | { 13 | if (parent.Array == null) 14 | throw new ArgumentNullException(nameof(parent), "Parent array is null."); 15 | if (offset < 0) 16 | throw new ArgumentOutOfRangeException(nameof(offset), "Negative offset."); 17 | if (count < 0) 18 | throw new ArgumentOutOfRangeException(nameof(count), "Negative count."); 19 | 20 | if (offset >= parent.Count) 21 | throw new ArgumentOutOfRangeException(nameof(offset), "Offset is beyond the end of the parent."); 22 | if (offset + count > parent.Count) 23 | throw new ArgumentOutOfRangeException(nameof(count), "Sub segment extends beyond the end of the parent."); 24 | 25 | return new ArraySegment(parent.Array, parent.Offset + offset, count); 26 | } 27 | 28 | public static T Get(this ArraySegment seg, int offset) 29 | { 30 | if (seg.Offset + offset >= seg.Count) 31 | throw new ArgumentOutOfRangeException(nameof(offset)); 32 | return seg.Array[seg.Offset + offset]; 33 | } 34 | 35 | public static void Set(this ArraySegment seg, int offset, T value) 36 | { 37 | if (seg.Offset + offset >= seg.Count) 38 | throw new ArgumentOutOfRangeException(nameof(offset)); 39 | seg.Array[seg.Offset + offset] = value; 40 | } 41 | 42 | public unsafe static void ZeroMemory(this ArraySegment dest) 43 | { 44 | fixed (byte* pDest = dest.Array) 45 | { 46 | Unsafe.InitBlock(pDest + dest.Offset, 0, (uint)dest.Count); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ZfsSharpLib/HardDisk/FileHardDisk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.MemoryMappedFiles; 4 | 5 | namespace ZfsSharpLib.HardDisks 6 | { 7 | unsafe class FileHardDisk : HardDisk 8 | { 9 | private MemoryMappedFile mFile; 10 | private MemoryMappedViewAccessor mViewAcessor; 11 | private byte* mPointer; 12 | private long mSize; 13 | 14 | public FileHardDisk(string path) 15 | { 16 | mFile = MemoryMappedFile.CreateFromFile(path, FileMode.Open); 17 | mViewAcessor = mFile.CreateViewAccessor(); 18 | long fileSize = new FileInfo(path).Length; 19 | //Limit the range of data we read to the Capacity of the ViewAccessor 20 | //in the unlikly case that it is smaller than the file size we read. 21 | //We can't just use the Capacity though, as it is round up to the page size. 22 | mSize = Math.Min(mViewAcessor.Capacity, fileSize); 23 | mPointer = null; 24 | mViewAcessor.SafeMemoryMappedViewHandle.AcquirePointer(ref mPointer); 25 | } 26 | 27 | public override void ReadBytes(Span dest, long offset) 28 | { 29 | CheckOffsets(offset, dest.Length); 30 | new Span(mPointer + offset, dest.Length).CopyTo(dest); 31 | } 32 | 33 | public override long Length 34 | { 35 | get { return mSize; } 36 | } 37 | 38 | public override void Dispose() 39 | { 40 | mPointer = null; 41 | mViewAcessor.SafeMemoryMappedViewHandle.ReleasePointer(); 42 | mViewAcessor.Dispose(); 43 | mFile.Dispose(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ZfsSharpLib/HardDisk/GptHardDrive.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | 7 | namespace ZfsSharpLib.HardDisks 8 | { 9 | class GptHardDrive : OffsetHardDisk 10 | { 11 | readonly static SortedSet sZfsPartitionTypes = new SortedSet(new[] { 12 | new Guid("6A898CC3-1DD2-11B2-99A6-080020736631"), //Solaris /usr (SmartOS partitions pools this way), also Mac ZFS apperently 13 | new Guid("516E7CBA-6ECF-11D6-8FF8-00022D09712B"), //FreeBSD ZFS 14 | }); 15 | readonly static Guid SolarisUsrPartitionId = new Guid("6A898CC3-1DD2-11B2-99A6-080020736631"); 16 | 17 | const string EfiMagic = "EFI PART"; 18 | const int CurrentRevision = 0x00010000; 19 | const int CurrentHeaderSize = 92; 20 | const int ParitionEntrySize = 128; 21 | const long SectorSize = 512; //TODO: DEAL WITH IT 22 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 23 | unsafe struct GptHeader 24 | { 25 | fixed byte signature[8]; 26 | public int Revision; 27 | public int HeaderSize; 28 | public int Crc; 29 | int Zero1; 30 | public long CurrentLba; 31 | public long BackupLba; 32 | public long FirstUsableLba; 33 | public long LastUsableLba; 34 | public Guid DiskGuid; 35 | public long StartingLbaOfPartitionEntries; 36 | public int NumberOfPartitions; 37 | public int SizeOfPartitionEntry; 38 | public int CrcOfPartitionEntry; 39 | 40 | public string Signature 41 | { 42 | get 43 | { 44 | fixed (byte* bytes = signature) 45 | return Marshal.PtrToStringAnsi(new IntPtr(bytes), 8); 46 | } 47 | } 48 | } 49 | 50 | [Flags] 51 | enum PartitionAttributes : long 52 | { 53 | None = 0, 54 | System = 1, 55 | Active = 1 << 2, 56 | ReadOnly = 1 << 60, 57 | Hidden = 1 << 62, 58 | DoNotAutomount = 1 << 63, 59 | } 60 | 61 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 62 | unsafe struct PartitionEntry 63 | { 64 | const int NameSize = 72; 65 | 66 | public Guid Type; 67 | public Guid ID; 68 | public long FirstLba; 69 | public long LastLba; 70 | public PartitionAttributes Attributes; 71 | fixed byte name[NameSize]; 72 | 73 | public string Name 74 | { 75 | get 76 | { 77 | byte[] copiedBytes = new byte[NameSize]; 78 | fixed (byte* bytes = name) 79 | { 80 | Marshal.Copy(new IntPtr(bytes), copiedBytes, 0, NameSize); 81 | } 82 | string ret = Encoding.GetEncoding("utf-16").GetString(copiedBytes, 0, NameSize); 83 | int subStr = ret.IndexOf('\0'); 84 | if (subStr != -1) 85 | ret = ret.Substring(0, subStr); 86 | return ret; 87 | } 88 | } 89 | } 90 | 91 | private GptHeader mHeader; 92 | private PartitionEntry mPartition; 93 | 94 | public GptHardDrive(HardDisk hdd) 95 | { 96 | //check for MBR protective partition 97 | if (!MbrHardDisk.IsMbr(hdd)) 98 | throw new Exception("Not MBR."); 99 | if (MbrHardDisk.GetType(hdd, 0) != MbrPartitionType.GptProtective) 100 | throw new Exception("Not GPT."); 101 | 102 | mHeader = LoadHeader(hdd, SectorSize); 103 | var backup = LoadHeader(hdd, hdd.Length - SectorSize); 104 | 105 | List parts = new List(); 106 | for (int i = 0; i < mHeader.NumberOfPartitions; i++) 107 | { 108 | var partEnt = GetLba(hdd, mHeader.StartingLbaOfPartitionEntries, i * mHeader.SizeOfPartitionEntry); 109 | if (partEnt.Type == Guid.Empty) 110 | continue; 111 | parts.Add(partEnt); 112 | } 113 | 114 | mPartition = parts.Where(p => sZfsPartitionTypes.Contains(p.Type)).Single(); 115 | 116 | Init(hdd, SectorSize * mPartition.FirstLba, SectorSize * (mPartition.LastLba - mPartition.FirstLba)); 117 | } 118 | 119 | private GptHeader LoadHeader(HardDisk hdd, long offset) 120 | { 121 | GptHeader ret; 122 | 123 | hdd.Get(offset, out ret); //LBA 1 124 | if (ret.Signature != EfiMagic) 125 | throw new Exception("Not a GPT."); 126 | if (ret.Revision != CurrentRevision) 127 | throw new Exception("Wrong rev."); 128 | if (ret.HeaderSize < CurrentHeaderSize) 129 | throw new Exception("Wrong header size."); 130 | //TODO: check crc 131 | if (ret.SizeOfPartitionEntry != ParitionEntrySize) 132 | throw new Exception("Wrong ParitionEntrySize."); 133 | //TODO: check partition entry CRC 134 | 135 | if (ret.NumberOfPartitions == 0) 136 | throw new Exception("No partitions!"); 137 | 138 | return ret; 139 | } 140 | 141 | private static T GetLba(HardDisk hdd, long absoluteLba, long extraOffset) where T : struct 142 | { 143 | long byteOffset = absoluteLba * SectorSize + extraOffset; 144 | T ret; 145 | hdd.Get(byteOffset, out ret); 146 | return ret; 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /ZfsSharpLib/HardDisk/HardDisk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ZfsSharpLib 4 | { 5 | abstract class HardDisk : IDisposable 6 | { 7 | protected void CheckOffsets(long offset, long size) 8 | { 9 | if (offset < 0 || size <= 0 || offset + size > Length) 10 | throw new ArgumentOutOfRangeException(); 11 | } 12 | 13 | public void Get(long offset, out T @struct) where T : struct 14 | { 15 | int structSize = Program.SizeOf(); 16 | CheckOffsets(offset, structSize); 17 | var bytes = Program.RentBytes(structSize); 18 | ReadBytes(bytes, offset); 19 | @struct = Program.ToStruct(bytes); 20 | Program.ReturnBytes(bytes); 21 | } 22 | 23 | /// 24 | /// Reads and verifies data from a label. 25 | /// 26 | /// 27 | /// 28 | /// true if the checksum is valid, false otherwise 29 | public bool ReadLabelBytes(ArraySegment dest, long offset) 30 | { 31 | ReadBytes(dest, offset); 32 | var verifier = new zio_cksum_t() 33 | { 34 | word1 = (ulong)offset, 35 | word2 = 0, 36 | word3 = 0, 37 | word4 = 0, 38 | }; 39 | return Zio.IsEmbeddedChecksumValid(dest, verifier); 40 | } 41 | 42 | public byte[] ReadBytes(long offset, int count) 43 | { 44 | var ret = new byte[count]; 45 | ReadBytes(new Span(ret), offset); 46 | return ret; 47 | } 48 | 49 | public abstract void ReadBytes(Span dest, long offset); 50 | 51 | public abstract long Length 52 | { 53 | get; 54 | } 55 | 56 | public abstract void Dispose(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ZfsSharpLib/HardDisk/MbrHardDisk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Runtime.InteropServices; 7 | using System.IO; 8 | 9 | namespace ZfsSharpLib.HardDisks 10 | { 11 | enum MbrPartitionType : byte 12 | { 13 | Empty = 0, 14 | Ntfs = 7, 15 | Solaris = 0xbf, 16 | GptProtective = 0xee, 17 | } 18 | #region StructStuff 19 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 20 | unsafe struct CHS 21 | { 22 | short Stuff1; 23 | byte Stuff2; 24 | } 25 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 26 | unsafe struct PartitionEntry 27 | { 28 | public byte Status; 29 | public CHS FirstSector; 30 | public MbrPartitionType Type; 31 | public CHS LastSector; 32 | public uint FirstSectorLba; 33 | public uint NumberOfSectors; 34 | } 35 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 36 | unsafe struct MbrHeader 37 | { 38 | public const ushort MbrMagic = 0xaa55; 39 | public const long SectorSize = 512; 40 | 41 | fixed byte BootstrapCode1[218]; 42 | short Zeros1; 43 | public byte OriginalPhysicalDrive; 44 | public byte Seconds; 45 | public byte Minutes; 46 | public byte Hours; 47 | fixed byte BootStrapCode2[216]; 48 | public int DiskSig; 49 | short Zeros2; 50 | public PartitionEntry Partition1; 51 | public PartitionEntry Partition2; 52 | public PartitionEntry Partition3; 53 | public PartitionEntry Partition4; 54 | public ushort BootSig; 55 | 56 | public PartitionEntry GetPartition(int index) 57 | { 58 | switch (index) 59 | { 60 | case 0: 61 | return Partition1; 62 | case 1: 63 | return Partition2; 64 | case 2: 65 | return Partition3; 66 | case 3: 67 | return Partition4; 68 | default: 69 | throw new ArgumentOutOfRangeException(); 70 | } 71 | } 72 | } 73 | #endregion 74 | 75 | class MbrHardDisk : OffsetHardDisk 76 | { 77 | private MbrHeader mHeader; 78 | private PartitionEntry mPartition; 79 | 80 | public MbrHardDisk(HardDisk hdd, int partition) 81 | { 82 | hdd.Get(0, out mHeader); 83 | 84 | //for now, always assume a GPT partition 85 | if (!MbrHardDisk.IsMbr(hdd)) 86 | throw new Exception("Expected a MBR hdd."); 87 | if (MbrHardDisk.GetType(hdd, 0) != MbrPartitionType.GptProtective) 88 | throw new Exception("Expected a GPT protective MBR entry."); 89 | 90 | mPartition = mHeader.GetPartition(partition); 91 | Init(hdd, (long)mPartition.FirstSectorLba * MbrHeader.SectorSize, (long)mPartition.NumberOfSectors * MbrHeader.SectorSize); 92 | } 93 | 94 | public static bool IsMbr(HardDisk hdd) 95 | { 96 | MbrHeader h; 97 | hdd.Get(0, out h); 98 | return h.BootSig == MbrHeader.MbrMagic; 99 | } 100 | 101 | public static MbrPartitionType GetType(HardDisk hdd, int index) 102 | { 103 | MbrHeader h; 104 | hdd.Get(0, out h); 105 | return h.GetPartition(index).Type; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ZfsSharpLib/HardDisk/OffsetHardDisk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ZfsSharpLib.HardDisks 4 | { 5 | class OffsetHardDisk : HardDisk 6 | { 7 | HardDisk mHdd; 8 | long mOffset; 9 | long mSize; 10 | 11 | public static HardDisk Create(HardDisk hdd, long offset, long size) 12 | { 13 | while (hdd is OffsetHardDisk) 14 | { 15 | var off = (OffsetHardDisk)hdd; 16 | offset += off.mOffset; 17 | hdd = off.mHdd; 18 | } 19 | return new OffsetHardDisk(hdd, offset, size); 20 | } 21 | 22 | private OffsetHardDisk(HardDisk hdd, long offset, long size) 23 | { 24 | Init(hdd, offset, size); 25 | } 26 | 27 | protected OffsetHardDisk() 28 | { 29 | } 30 | 31 | protected void Init(HardDisk hdd, long offset, long size) 32 | { 33 | if (offset < 0) 34 | throw new ArgumentOutOfRangeException(); 35 | if (offset + size > hdd.Length) 36 | throw new ArgumentOutOfRangeException(); 37 | 38 | mHdd = hdd; 39 | mOffset = offset; 40 | mSize = size; 41 | } 42 | 43 | public override void ReadBytes(Span dest, long offset) 44 | { 45 | CheckOffsets(offset, dest.Length); 46 | mHdd.ReadBytes(dest, mOffset + offset); 47 | } 48 | 49 | public override long Length 50 | { 51 | get { return mSize; } 52 | } 53 | 54 | public override void Dispose() 55 | { 56 | mHdd.Dispose(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ZfsSharpLib/HardDisk/OffsetTableHardDisk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ZfsSharpLib 8 | { 9 | abstract class OffsetTableHardDisk : HardDisk 10 | { 11 | protected readonly HardDisk mHdd; 12 | readonly Program.BlockReader mReadBlock; 13 | readonly Func mGetBlockKey; 14 | 15 | //need to be set by subclass 16 | protected long[] mBlockOffsets; 17 | protected int mBlockSize; 18 | 19 | protected OffsetTableHardDisk(HardDisk hdd) 20 | { 21 | if (hdd == null) 22 | throw new ArgumentNullException(nameof(hdd)); 23 | mHdd = hdd; 24 | mReadBlock = readBlock; 25 | mGetBlockKey = getBlockKey; 26 | } 27 | 28 | public override long Length 29 | { 30 | get 31 | { 32 | return mBlockSize * mBlockOffsets.LongLength; 33 | } 34 | } 35 | 36 | public override void Dispose() 37 | { 38 | mHdd.Dispose(); 39 | } 40 | 41 | public override void ReadBytes(Span dest, long offset) 42 | { 43 | CheckOffsets(offset, dest.Length); 44 | Program.MultiBlockCopy(dest, offset, mBlockSize, mGetBlockKey, mReadBlock); 45 | } 46 | 47 | long getBlockKey(long blockId) 48 | { 49 | return mBlockOffsets[blockId]; 50 | } 51 | 52 | void readBlock(Span array, long blockOffset, int blockStartNdx) 53 | { 54 | if (blockOffset == -1) 55 | { 56 | array.Fill(0); 57 | } 58 | else 59 | { 60 | mHdd.ReadBytes(array, blockOffset + blockStartNdx); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ZfsSharpLib/HardDisk/VdiHardDisk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace ZfsSharpLib.HardDisks 6 | { 7 | class VdiHardDisk : OffsetTableHardDisk 8 | { 9 | enum ImageType : uint 10 | { 11 | Dynamic = 0x01, 12 | Fixed = 0x02, 13 | } 14 | 15 | const uint VdiMagic = 0xbeda107f; 16 | const uint VdiHeadSize = 0x190; 17 | const uint VdiVersion = 0x00010001; 18 | 19 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 20 | unsafe struct VdiHeader 21 | { 22 | fixed byte Text[0x40]; 23 | 24 | public uint ImageSig; 25 | public uint Version; 26 | public uint SizeOfHeader; 27 | public ImageType ImageType; 28 | 29 | public uint ImageFlags; 30 | fixed byte ImageDescription[0x100]; 31 | public uint OffsetBlocks; 32 | public uint OffsetData; 33 | uint Cylinders; 34 | 35 | uint Heads; 36 | uint Sectors; 37 | public uint SectorSize; 38 | uint pad; 39 | 40 | public ulong DiskSizeInBytes; 41 | public uint BlockSize; 42 | uint BlockExtraData; 43 | 44 | public uint BlocksInHdd; 45 | public uint BlocksAllocated; 46 | public Guid Uuid; 47 | public Guid UuidOfLastSnap; 48 | public Guid UuidLink; 49 | public Guid UuidParent; 50 | } 51 | 52 | unsafe public VdiHardDisk(HardDisk hdd) 53 | : base(hdd) 54 | { 55 | var headBytes = hdd.ReadBytes(0, sizeof(VdiHeader)); 56 | VdiHeader head = Program.ToStruct(headBytes); 57 | 58 | if (head.ImageSig != VdiMagic) 59 | throw new Exception("Wrong magic."); 60 | if (head.Version != VdiVersion) 61 | throw new Exception("Wrong version."); 62 | if (head.SizeOfHeader != VdiHeadSize) 63 | throw new Exception("Wrong size."); 64 | 65 | if (head.ImageType != ImageType.Dynamic) 66 | throw new NotImplementedException("Only dynamic is supported."); 67 | 68 | var dataOffset = head.OffsetData; 69 | mBlockOffsets = new long[head.BlocksInHdd]; 70 | mBlockSize = (int)head.BlockSize; 71 | 72 | for (long i = 0; i < head.BlocksInHdd; i++) 73 | { 74 | uint blockLoc; 75 | hdd.Get(head.OffsetBlocks + i * 4, out blockLoc); 76 | if (blockLoc == ~0u) 77 | mBlockOffsets[i] = -1; 78 | else 79 | mBlockOffsets[i] = dataOffset + blockLoc * mBlockSize; 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ZfsSharpLib/HardDisk/VhdHardDisk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers.Binary; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace ZfsSharpLib.HardDisks 6 | { 7 | static class VhdHardDisk 8 | { 9 | #region Structs 10 | [Flags] 11 | enum Features : int 12 | { 13 | None = 0, 14 | Temp = 1, 15 | Reserved = 2, 16 | } 17 | enum CreatorOs : int 18 | { 19 | Win = 0x5769326B, //(Wi2k) 20 | Max = 0x4D616320, 21 | } 22 | enum DiskType : int 23 | { 24 | None = 0, 25 | Fixed = 2, 26 | Dynamic = 3, 27 | Differencing = 4, 28 | } 29 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 30 | unsafe struct VhdHeader 31 | { 32 | public fixed byte Cookie[8]; 33 | public Features Features; 34 | public int FileFormatVersion; 35 | public long DataOffset; 36 | public int TimeStamp; 37 | public fixed byte CreatorApp[4]; 38 | public int CreatorVersion; 39 | public CreatorOs CreatorOs; 40 | public long OriginalSize; 41 | public long CurrentSize; 42 | public int DiskGeometry; 43 | public DiskType DiskType; 44 | public int Checksum; 45 | public fixed byte UniqueId[16]; 46 | public byte SavedState; 47 | fixed byte Reserved[427]; 48 | 49 | public string CookieStr 50 | { 51 | get 52 | { 53 | fixed (byte* bytes = Cookie) 54 | { 55 | return Marshal.PtrToStringAnsi(new IntPtr(bytes), 8); 56 | } 57 | } 58 | } 59 | 60 | public string CreatorAppStr 61 | { 62 | get 63 | { 64 | fixed (byte* bytes = CreatorApp) 65 | { 66 | return Marshal.PtrToStringAnsi(new IntPtr(bytes), 4); 67 | } 68 | } 69 | } 70 | } 71 | 72 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 73 | unsafe struct DynamicHeader 74 | { 75 | fixed byte Cookie[8]; 76 | public ulong DataOffset; 77 | public long TableOffset; 78 | public int HeaderVersion; 79 | public int MaxTableEntries; 80 | public int BlockSize; 81 | public int Checksum; 82 | public Guid ParentUniqueID; 83 | public int ParentTimeStamp; 84 | int Reserved; 85 | fixed byte ParentUnicodeName[512]; 86 | fixed byte LocatorEntries[8 * 24]; // for differencing disks only 87 | fixed byte Reserved2[256]; 88 | 89 | public string CookieStr 90 | { 91 | get 92 | { 93 | fixed (byte* bytes = Cookie) 94 | { 95 | return Marshal.PtrToStringAnsi(new IntPtr(bytes), 8); 96 | } 97 | } 98 | } 99 | 100 | public string ParentUnicodeNameStr 101 | { 102 | get 103 | { 104 | fixed (byte* bytes = ParentUnicodeName) 105 | { 106 | return Marshal.PtrToStringAnsi(new IntPtr(bytes), 512); 107 | } 108 | } 109 | } 110 | } 111 | #endregion 112 | 113 | public static HardDisk Create(HardDisk hdd) 114 | { 115 | byte[] headBytes = hdd.ReadBytes(hdd.Length - 512, 512); 116 | VhdHeader head = Program.ToStructFromBigEndian(headBytes); 117 | if (head.CookieStr != "conectix") 118 | throw new Exception("missing magic string"); 119 | if (head.FileFormatVersion != 0x00010000) 120 | throw new Exception("upsupported version"); 121 | //TODO: validate checksum 122 | 123 | if (head.DiskType == DiskType.Fixed) 124 | { 125 | return new FixedVhd(hdd, in head); 126 | } 127 | else if (head.DiskType == DiskType.Dynamic) 128 | { 129 | return new DynamicVhd(hdd, in head); 130 | } 131 | else 132 | { 133 | throw new Exception("Only fixed size VHDs are supported."); 134 | } 135 | } 136 | 137 | class FixedVhd : OffsetHardDisk 138 | { 139 | public FixedVhd(HardDisk hdd, in VhdHeader head) 140 | { 141 | long size = hdd.Length - 512; 142 | 143 | if (head.CurrentSize != size) 144 | throw new Exception(); 145 | 146 | Init(hdd, 0, size); 147 | } 148 | } 149 | 150 | class DynamicVhd : OffsetTableHardDisk 151 | { 152 | const int SECTOR_SIZE = 512; 153 | 154 | long mSize; 155 | 156 | public DynamicVhd(HardDisk hdd, in VhdHeader head) 157 | : base(hdd) 158 | { 159 | int dySize = Program.SizeOf(); 160 | DynamicHeader dyhead = Program.ToStructFromBigEndian(hdd.ReadBytes(head.DataOffset, dySize)); 161 | if (dyhead.CookieStr != "cxsparse") 162 | throw new Exception("missing magic string"); 163 | if (dyhead.HeaderVersion != 0x00010000) 164 | throw new NotSupportedException("wrong version"); 165 | if (dyhead.ParentUniqueID != Guid.Empty) 166 | throw new NotSupportedException("Differencing disks not supported."); 167 | //TODO: validate checksum 168 | 169 | mSize = head.CurrentSize; 170 | mBlockSize = dyhead.BlockSize; 171 | if (mBlockSize % SECTOR_SIZE != 0) 172 | throw new Exception("Block size is not a multiple of sector size."); 173 | int sectorBitmapSize = (mBlockSize / SECTOR_SIZE) / 8; 174 | 175 | //Round up if we have a partial block 176 | int numberOfBlocks = (int)((mSize + mBlockSize - 1) / mBlockSize); 177 | if (numberOfBlocks != dyhead.MaxTableEntries) 178 | throw new Exception("Our calculated number of blocks does not match the MaxTableEntries. Is that right?"); 179 | mBlockOffsets = new long[numberOfBlocks]; 180 | 181 | var bat = hdd.ReadBytes(dyhead.TableOffset, numberOfBlocks * 4); 182 | 183 | for (int i = 0; i < numberOfBlocks; i++) 184 | { 185 | long batEntry = BinaryPrimitives.ReadInt32BigEndian(new ReadOnlySpan(bat, i * 4, 4)); 186 | if (batEntry != -1) 187 | { 188 | batEntry *= SECTOR_SIZE; 189 | //skip the sector bitmap, since we don't support differencing disks 190 | batEntry += sectorBitmapSize; 191 | } 192 | mBlockOffsets[i] = batEntry; 193 | } 194 | } 195 | 196 | public override long Length 197 | { 198 | get { return mSize; } 199 | } 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /ZfsSharpLib/LeafVdevInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using ZfsSharpLib.HardDisks; 7 | 8 | namespace ZfsSharpLib 9 | { 10 | class LeafVdevInfo : IDisposable 11 | { 12 | const int VDEV_PAD_SIZE = (8 << 10); 13 | /* 2 padding areas (vl_pad1 and vl_pad2) to skip */ 14 | const int VDEV_SKIP_SIZE = VDEV_PAD_SIZE * 2; 15 | const int VDEV_PHYS_SIZE = (112 << 10); 16 | 17 | //uberblocks can be between 1k and 8k 18 | const int UBERBLOCK_SHIFT = 10; 19 | const int MAX_UBERBLOCK_SHIFT = 13; 20 | const int VDEV_UBERBLOCK_RING = (128 << 10); 21 | 22 | public LeafVdevInfo(HardDisk hdd) 23 | { 24 | this.HDD = hdd; 25 | 26 | var rentedBytes = Program.RentBytes(VDEV_PHYS_SIZE); 27 | 28 | try 29 | { 30 | if (!hdd.ReadLabelBytes(rentedBytes, VDEV_SKIP_SIZE)) 31 | throw new Exception("Invalid checksum on lable config data!"); 32 | Config = new NvList(rentedBytes); 33 | } 34 | finally 35 | { 36 | Program.ReturnBytes(rentedBytes); 37 | rentedBytes = default(ArraySegment); 38 | } 39 | 40 | //figure out how big the uber blocks are 41 | var vdevTree = Config.Get("vdev_tree"); 42 | var ubShift = (int)vdevTree.Get("ashift"); 43 | ubShift = Math.Max(ubShift, UBERBLOCK_SHIFT); 44 | ubShift = Math.Min(ubShift, MAX_UBERBLOCK_SHIFT); 45 | var ubSize = 1 << ubShift; 46 | var ubCount = VDEV_UBERBLOCK_RING >> ubShift; 47 | 48 | List blocks = new List(); 49 | var ubBytes = Program.RentBytes(ubSize); 50 | try 51 | { 52 | for (long i = 0; i < ubCount; i++) 53 | { 54 | var offset = VDEV_SKIP_SIZE + VDEV_PHYS_SIZE + ubSize * i; 55 | if (!hdd.ReadLabelBytes(ubBytes, offset)) 56 | continue; 57 | uberblock_t b = Program.ToStruct(ubBytes.Array, ubBytes.Offset); 58 | if (b.Magic == uberblock_t.UbMagic) 59 | { 60 | blocks.Add(b); 61 | } 62 | } 63 | } 64 | finally 65 | { 66 | Program.ReturnBytes(ubBytes); 67 | ubBytes = default(ArraySegment); 68 | } 69 | this.Uberblock = blocks.OrderByDescending(u => u.Txg).ThenByDescending(u => u.TimeStamp).First(); 70 | 71 | const int VDevLableSizeStart = 4 << 20; 72 | const int VDevLableSizeEnd = 512 << 10; 73 | hdd = OffsetHardDisk.Create(hdd, VDevLableSizeStart, hdd.Length - VDevLableSizeStart - VDevLableSizeEnd); 74 | this.HDD = hdd; 75 | } 76 | 77 | public ulong Guid { get; private set; } 78 | public uberblock_t Uberblock { get; private set; } 79 | public HardDisk HDD { get; private set; } 80 | public NvList Config { get; private set; } 81 | 82 | public void Dispose() 83 | { 84 | HDD.Dispose(); 85 | } 86 | 87 | static readonly Dictionary> sFileFormats = new Dictionary>(StringComparer.OrdinalIgnoreCase) 88 | { 89 | { ".vhd", fileHdd => new GptHardDrive(VhdHardDisk.Create(fileHdd)) }, 90 | { ".vhdx", fileHdd => new GptHardDrive(new VhdxHardDisk(fileHdd)) }, 91 | { ".vdi", fileHdd => new GptHardDrive(new VdiHardDisk(fileHdd)) }, 92 | { ".zfs", fileHdd => fileHdd }, 93 | }; 94 | 95 | public static List GetLeafVdevs(string dirOrFile) 96 | { 97 | var ret = new List(); 98 | 99 | try 100 | { 101 | FileInfo[] files; 102 | if (File.Exists(dirOrFile)) 103 | { 104 | files = new FileInfo[] { new FileInfo(dirOrFile) }; 105 | } 106 | else 107 | { 108 | files = new DirectoryInfo(dirOrFile).GetFiles(); 109 | } 110 | 111 | foreach (var fi in files) 112 | { 113 | Func factory; 114 | if (!sFileFormats.TryGetValue(fi.Extension, out factory)) 115 | continue; 116 | 117 | var file = new FileHardDisk(fi.FullName); 118 | var partition = factory(file); 119 | var vdev = new LeafVdevInfo(partition); 120 | ret.Add(vdev); 121 | } 122 | } 123 | catch 124 | { 125 | foreach (var leaf in ret) 126 | { 127 | leaf.Dispose(); 128 | } 129 | throw; 130 | } 131 | 132 | return ret; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /ZfsSharpLib/MetaSlabs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ZfsSharpLib 4 | { 5 | class MetaSlabs 6 | { 7 | RangeMap[] mRangeMap; 8 | ObjectSet mMos; 9 | long mSlabSize; 10 | 11 | public MetaSlabs(ObjectSet mos, long metaSlabArray, int metaSlabShift, int aShift) 12 | { 13 | mMos = mos; 14 | mSlabSize = 1L << metaSlabShift; 15 | 16 | var slabDnode = mos.ReadEntry(metaSlabArray); 17 | var someBytes = Program.RentBytes(checked((int)slabDnode.AvailableDataSize)); 18 | slabDnode.Read(someBytes, 0); 19 | 20 | int numberOfSlabs = someBytes.Count / 8; 21 | mRangeMap = new RangeMap[numberOfSlabs]; 22 | long[] ids = new long[numberOfSlabs]; 23 | Buffer.BlockCopy(someBytes.Array, someBytes.Offset, ids, 0, someBytes.Count); 24 | 25 | Program.ReturnBytes(someBytes); 26 | someBytes = default(ArraySegment); 27 | 28 | for (int i = 0; i < numberOfSlabs; i++) 29 | { 30 | var id = ids[i]; 31 | RangeMap map; 32 | if (id == 0) 33 | { 34 | map = new RangeMap(); 35 | } 36 | else 37 | { 38 | map = LoadEntrysForMetaSlab(id, aShift); 39 | } 40 | mRangeMap[i] = map; 41 | } 42 | } 43 | 44 | public bool ContainsRange(long offset, long range) 45 | { 46 | long slabNdx = offset / mSlabSize; 47 | offset = offset % mSlabSize; 48 | return mRangeMap[slabNdx].ContainsRange((ulong)offset, (ulong)range); 49 | } 50 | 51 | unsafe RangeMap LoadEntrysForMetaSlab(long dnEntry, int sm_shift) 52 | { 53 | RangeMap ret = new RangeMap(); 54 | 55 | var dn = mMos.ReadEntry(dnEntry); 56 | if (dn.Type != dmu_object_type_t.SPACE_MAP || dn.BonusType != dmu_object_type_t.SPACE_MAP_HEADER) 57 | throw new Exception("Not a space map."); 58 | 59 | var head = dn.GetBonus(); 60 | 61 | if (head.smo_object != dnEntry) 62 | throw new Exception(); 63 | 64 | if (head.smo_objsize > int.MaxValue) 65 | throw new Exception("Holy cow, this space map is greater than 2GB, what is wrong with your VDev!?!?"); 66 | 67 | var someBytes = Program.RentBytes((int)head.smo_objsize); 68 | dn.Read(someBytes, 0); 69 | for (int i = 0; i < someBytes.Count; i += 8) 70 | { 71 | var ent = Program.ToStruct(someBytes.SubSegment(i, sizeof(spaceMapEntry))); 72 | if (ent.IsDebug) 73 | continue; 74 | 75 | ulong offset = (ent.Offset << sm_shift); 76 | ulong range = ent.Run << sm_shift; 77 | //Console.WriteLine("\t [{4,6}] {0} range: {1:x10}-{2:x10} size: {3:x6}", ent.Type, offset, offset + range, range, i / 8); 78 | if (ent.Type == SpaceMapEntryType.A) 79 | { 80 | ret.AddRange(offset, range); 81 | } 82 | else if (ent.Type == SpaceMapEntryType.F) 83 | { 84 | ret.RemoveRange(offset, range); 85 | } 86 | } 87 | Program.ReturnBytes(someBytes); 88 | 89 | return ret; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ZfsSharpLib/NvList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace ZfsSharpLib 7 | { 8 | //based on usr\src\uts\common\rpc\xdr.c 9 | 10 | /// 11 | /// Decodes an XDR representation of a NVList. 12 | /// 13 | class NvList : IEnumerable> 14 | { 15 | const int NV_VERSION = 0; 16 | 17 | enum NvDataType 18 | { 19 | UNKNOWN = 0, 20 | BOOLEAN, 21 | BYTE, 22 | INT16, 23 | UINT16, 24 | INT32, 25 | UINT32, 26 | INT64, 27 | UINT64, 28 | STRING, 29 | BYTE_ARRAY, 30 | INT16_ARRAY, 31 | UINT16_ARRAY, 32 | INT32_ARRAY, 33 | UINT32_ARRAY, 34 | INT64_ARRAY, 35 | UINT64_ARRAY, 36 | STRING_ARRAY, 37 | HRTIME, 38 | NVLIST, 39 | NVLIST_ARRAY, 40 | BOOLEAN_VALUE, 41 | INT8, 42 | UINT8, 43 | BOOLEAN_ARRAY, 44 | INT8_ARRAY, 45 | UINT8_ARRAY, 46 | } 47 | 48 | [Flags] 49 | enum NvFlags : int 50 | { 51 | None = 0, 52 | UNIQUE_NAME = 0x1, 53 | UNIQUE_NAME_TYPE = 0x2, 54 | } 55 | 56 | enum NV_ENCODE : byte 57 | { 58 | NATIVE = 0, 59 | XDR = 1, 60 | } 61 | 62 | private Dictionary mVals = new Dictionary(); 63 | 64 | public NvList(ArraySegment bytes) 65 | : this(new MemoryStream(bytes.Array, bytes.Offset, bytes.Count)) 66 | { 67 | } 68 | 69 | public NvList(byte[] bytes) 70 | : this(new MemoryStream(bytes)) 71 | { 72 | } 73 | 74 | public NvList(Stream s) 75 | { 76 | var r = new NvListBinaryReader(s, Encoding.ASCII); 77 | 78 | NV_ENCODE encoding = (NV_ENCODE)r.ReadByte(); 79 | if (encoding != NV_ENCODE.XDR) 80 | throw new Exception("Is not encoding in XDR."); 81 | byte endian = r.ReadByte(); 82 | if (endian != 1) 83 | throw new Exception("Incorrect endianness."); 84 | short reserved = r.ReadInt16(); //reserved fields 85 | 86 | Load(r); 87 | } 88 | 89 | private NvList(NvListBinaryReader r) 90 | { 91 | Load(r); 92 | } 93 | 94 | 95 | private void Load(NvListBinaryReader r) 96 | { 97 | int version = r.ReadInt32(); 98 | 99 | if (version != NV_VERSION) 100 | throw new NotSupportedException("Unsupport NVList version!"); 101 | 102 | NvFlags flags = (NvFlags)r.ReadInt32(); 103 | 104 | while (true) 105 | { 106 | int encodedSize = r.ReadInt32(); 107 | int decodedSize = r.ReadInt32(); 108 | 109 | if (encodedSize == 0 && decodedSize == 0) 110 | break; 111 | 112 | string name = r.ReadString(); 113 | NvDataType type = (NvDataType)r.ReadInt32(); 114 | int numberOfElements = r.ReadInt32(); 115 | 116 | object val; 117 | switch (type) 118 | { 119 | case NvDataType.STRING: 120 | val = r.ReadString(); 121 | break; 122 | case NvDataType.UINT64: 123 | val = r.ReadUInt64(); 124 | break; 125 | case NvDataType.NVLIST: 126 | val = new NvList(r); 127 | break; 128 | case NvDataType.NVLIST_ARRAY: 129 | var array = new NvList[numberOfElements]; 130 | for (int i = 0; i < numberOfElements; i++) 131 | { 132 | array[i] = new NvList(r); 133 | } 134 | val = array; 135 | break; 136 | case NvDataType.BOOLEAN: 137 | val = true; 138 | break; 139 | case NvDataType.BOOLEAN_VALUE: 140 | val = r.ReadInt32() != 0; 141 | break; 142 | default: 143 | throw new NotImplementedException(); 144 | } 145 | mVals.Add(name, val); 146 | } 147 | } 148 | 149 | public T Get(string name) 150 | { 151 | return (T)mVals[name]; 152 | } 153 | 154 | public T? GetOptional(string name) where T : struct 155 | { 156 | if (!mVals.ContainsKey(name)) 157 | return new Nullable(); 158 | return (T)mVals[name]; 159 | } 160 | 161 | public IEnumerator> GetEnumerator() 162 | { 163 | return mVals.GetEnumerator(); 164 | } 165 | 166 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 167 | { 168 | return mVals.GetEnumerator(); 169 | } 170 | 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /ZfsSharpLib/NvListBinaryReader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace ZfsSharpLib 5 | { 6 | class NvListBinaryReader : BinaryReader 7 | { 8 | readonly Encoding mEnc; 9 | public NvListBinaryReader(Stream s) 10 | : base(s, Encoding.UTF8) 11 | { 12 | } 13 | public NvListBinaryReader(Stream s, Encoding enc) 14 | : base(s, enc) 15 | { 16 | mEnc = enc; 17 | } 18 | 19 | public override short ReadInt16() 20 | { 21 | byte b0 = ReadByte(); 22 | byte b1 = ReadByte(); 23 | return (short)((b0 << 8) | b1); 24 | } 25 | 26 | public override int ReadInt32() 27 | { 28 | byte b0 = ReadByte(); 29 | byte b1 = ReadByte(); 30 | byte b2 = ReadByte(); 31 | byte b3 = ReadByte(); 32 | return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; 33 | } 34 | 35 | public override long ReadInt64() 36 | { 37 | long b0 = ReadByte(); 38 | long b1 = ReadByte(); 39 | long b2 = ReadByte(); 40 | long b3 = ReadByte(); 41 | long b4 = ReadByte(); 42 | long b5 = ReadByte(); 43 | long b6 = ReadByte(); 44 | long b7 = ReadByte(); 45 | return (b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | (b4 << 24) | (b5 << 16) | (b6 << 8) | b7; 46 | } 47 | 48 | public override ushort ReadUInt16() 49 | { 50 | byte b0 = ReadByte(); 51 | byte b1 = ReadByte(); 52 | return (ushort)((b0 << 8) | b1); 53 | } 54 | 55 | public override uint ReadUInt32() 56 | { 57 | byte b0 = ReadByte(); 58 | byte b1 = ReadByte(); 59 | byte b2 = ReadByte(); 60 | byte b3 = ReadByte(); 61 | return (uint)((b0 << 24) | (b1 << 16) | (b2 << 8) | b3); 62 | } 63 | 64 | public override ulong ReadUInt64() 65 | { 66 | ulong b0 = ReadByte(); 67 | ulong b1 = ReadByte(); 68 | ulong b2 = ReadByte(); 69 | ulong b3 = ReadByte(); 70 | ulong b4 = ReadByte(); 71 | ulong b5 = ReadByte(); 72 | ulong b6 = ReadByte(); 73 | ulong b7 = ReadByte(); 74 | return (b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | (b4 << 24) | (b5 << 16) | (b6 << 8) | b7; 75 | } 76 | 77 | public override string ReadString() 78 | { 79 | int size = ReadInt32(); 80 | byte[] bytes = ReadBytes((size + 3) & ~3); 81 | return mEnc.GetString(bytes, 0, size); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ZfsSharpLib/ObjectSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ZfsSharpLib 7 | { 8 | class ObjectSet 9 | { 10 | readonly DNode mMetaDNode; 11 | readonly Zio mZio; 12 | readonly dmu_objset_type_t mType; 13 | 14 | public ObjectSet(Zio zio, objset_phys_t os) 15 | { 16 | if (zio == null) 17 | throw new ArgumentNullException("zio"); 18 | 19 | mZio = zio; 20 | mType = os.Type; 21 | mMetaDNode = new DNode(zio, os.MetaDnode); 22 | } 23 | 24 | public dmu_objset_type_t Type 25 | { 26 | get { return mType; } 27 | } 28 | 29 | public unsafe DNode ReadEntry(long index) 30 | { 31 | var buf = Program.RentBytes(sizeof(dnode_phys_t)); 32 | try 33 | { 34 | mMetaDNode.Read(buf, index << dnode_phys_t.DNODE_SHIFT); 35 | return new DNode(mZio, Program.ToStruct(buf)); 36 | } 37 | finally 38 | { 39 | Program.ReturnBytes(buf); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ZfsSharpLib/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.InteropServices; 8 | 9 | namespace ZfsSharpLib 10 | { 11 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 12 | struct uberblock_t 13 | { 14 | public ulong Magic; 15 | public ulong Version; 16 | public ulong Txg; 17 | public ulong GuidSum; 18 | public ulong TimeStamp; 19 | public blkptr_t rootbp; 20 | 21 | public const ulong UbMagic = 0x00bab10c; 22 | } 23 | 24 | static class Program 25 | { 26 | static readonly ArrayPool sBytePool = ArrayPool.Shared; 27 | 28 | public static ArraySegment RentBytes(int size) 29 | { 30 | return new ArraySegment(sBytePool.Rent(size), 0, size); 31 | } 32 | 33 | public static void ReturnBytes(ArraySegment buffer) 34 | { 35 | sBytePool.Return(buffer.Array); 36 | } 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | public static int SizeOf() 40 | { 41 | return Unsafe.SizeOf(); 42 | } 43 | 44 | public static T ToStruct(byte[] bytes) where T : struct 45 | { 46 | return ToStruct(bytes, 0); 47 | } 48 | 49 | public unsafe static T ToStruct(byte[] bytes, long offset) where T : struct 50 | { 51 | fixed (byte* ptr = bytes) 52 | return ToStruct(ptr, offset, bytes.Length); 53 | } 54 | 55 | public static T ToStruct(ArraySegment bytes) where T : struct 56 | { 57 | if (Unsafe.SizeOf() != bytes.Count) 58 | throw new ArgumentOutOfRangeException(); 59 | return ToStruct(bytes.Array, bytes.Offset); 60 | } 61 | 62 | public static T ToStruct(Span bytes) where T : struct 63 | { 64 | if (Unsafe.SizeOf() != bytes.Length) 65 | throw new ArgumentOutOfRangeException(); 66 | return MemoryMarshal.Cast(bytes)[0]; 67 | } 68 | 69 | public unsafe static T ToStruct(byte* ptr, long offset, long ptrLength) where T : struct 70 | { 71 | if (offset < 0 || ptrLength <= 0) 72 | throw new ArgumentOutOfRangeException(); 73 | if (offset + Unsafe.SizeOf() > ptrLength) 74 | throw new ArgumentOutOfRangeException(); 75 | return Unsafe.Read(ptr + offset); 76 | } 77 | 78 | public static T ToStructFromBigEndian(byte[] bytes) where T : struct 79 | { 80 | if (BitConverter.IsLittleEndian) 81 | { 82 | return ToStructByteSwap(bytes); 83 | } 84 | else 85 | { 86 | return ToStruct(bytes); 87 | } 88 | } 89 | 90 | public static T ToStructByteSwap(byte[] bytes) where T : struct 91 | { 92 | var copy = new byte[bytes.Length]; 93 | Buffer.BlockCopy(bytes, 0, copy, 0, copy.Length); 94 | foreach (var f in typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) 95 | { 96 | ByteSwapField(f.Name, f.FieldType, copy); 97 | } 98 | return ToStruct(copy); 99 | } 100 | 101 | static Dictionary sStructSize = new Dictionary() 102 | { 103 | { typeof(byte), 1 }, 104 | { typeof(sbyte), 1 }, 105 | { typeof(short), 2 }, 106 | { typeof(ushort), 2 }, 107 | { typeof(int), 4 }, 108 | { typeof(uint), 4 }, 109 | { typeof(long), 8 }, 110 | { typeof(ulong), 8 }, 111 | { typeof(Guid), 16 }, 112 | }; 113 | 114 | static void ByteSwapField(string fieldName, Type fieldType, byte[] byteArray) where T : struct 115 | { 116 | var itemOffset = Marshal.OffsetOf(typeof(T), fieldName).ToInt32(); 117 | ByteSwap(fieldType, byteArray, itemOffset); 118 | } 119 | 120 | public static void ByteSwap(Type type, byte[] byteArray, int itemOffset) 121 | { 122 | int itemSize; 123 | if (!sStructSize.TryGetValue(type, out itemSize)) 124 | { 125 | if (type.IsEnum) 126 | { 127 | var realType = type.GetEnumUnderlyingType(); 128 | ByteSwap(realType, byteArray, itemOffset); 129 | return; 130 | } 131 | else if (type.GetCustomAttributes(typeof(UnsafeValueTypeAttribute), false).Length != 0) 132 | { 133 | return; 134 | //ignore fixed size buffers 135 | } 136 | else 137 | throw new NotSupportedException(); 138 | } 139 | 140 | for (int byteNdx = 0; byteNdx < itemSize / 2; byteNdx++) 141 | { 142 | int lowerNdx = itemOffset + byteNdx; 143 | int higherNdx = itemOffset + itemSize - byteNdx - 1; 144 | byte b = byteArray[lowerNdx]; 145 | byteArray[lowerNdx] = byteArray[higherNdx]; 146 | byteArray[higherNdx] = b; 147 | } 148 | } 149 | 150 | /// 151 | /// 152 | /// 153 | /// The place to store the read data. 154 | /// An identifier for the block. 155 | /// The offset within the block to start reading from. 156 | public delegate void BlockReader(Span dest, T blockKey, int startNdx); 157 | 158 | static long roundup(long x, long y) 159 | { 160 | return ((x + (y - 1)) / y) * y; 161 | } 162 | 163 | static long rounddown(long x, long y) 164 | { 165 | return (x / y) * y; 166 | } 167 | 168 | /// 169 | /// Given a large amount of data stored in equal sized blocks, reads a subset of that data efficiently. 170 | /// 171 | /// 172 | /// The place to store the read data. 173 | /// The byte offset into the blocks to read. 174 | /// The size of the blocks. 175 | /// Given a block offset returns a key for reading that block. 176 | /// Given a block key, reads the block. 177 | public static void MultiBlockCopy(Span dest, long offset, int blockSize, Func GetBlockKey, BlockReader ReadBlock) 178 | { 179 | if (offset < 0) 180 | throw new ArgumentOutOfRangeException("offset"); 181 | if (blockSize <= 0) 182 | throw new ArgumentOutOfRangeException("blockSize"); 183 | 184 | long firstBlock = offset / blockSize; 185 | int numBlocks = (int)((roundup(offset + dest.Length, blockSize) - rounddown(offset, blockSize)) / blockSize); 186 | 187 | int remaingBytes = dest.Length; 188 | int destOffset = 0; 189 | for (int i = 0; i < numBlocks; i++) 190 | { 191 | int blockOffset = (int)(offset % blockSize); 192 | int size = Math.Min(remaingBytes, blockSize - blockOffset); 193 | 194 | var key = GetBlockKey(firstBlock + i); 195 | ReadBlock(dest.Slice(destOffset, size), key, blockOffset); 196 | 197 | destOffset += size; 198 | offset += size; 199 | remaingBytes -= size; 200 | } 201 | } 202 | 203 | public const int SPA_MINBLOCKSHIFT = 9; 204 | public const int SPA_MAXBLOCKSHIFT = 17; 205 | public const int SPA_MINBLOCKSIZE = (1 << SPA_MINBLOCKSHIFT); 206 | const long SPA_MAXBLOCKSIZE = (1L << SPA_MAXBLOCKSHIFT); 207 | } 208 | 209 | 210 | } 211 | -------------------------------------------------------------------------------- /ZfsSharpLib/RangeMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.DataStructures; 3 | using System.Diagnostics; 4 | 5 | namespace ZfsSharpLib 6 | { 7 | class RangeMap 8 | { 9 | private readonly AvlTree mTree = new AvlTree(); 10 | 11 | #if DEBUG 12 | static RangeMap() 13 | { 14 | var space = new SpaceRange(100, 100); 15 | 16 | Debug.Assert(space.Intersects(new SpaceRange(150, 25))); 17 | Debug.Assert(space.Intersects(new SpaceRange(100, 25))); 18 | Debug.Assert(space.Intersects(new SpaceRange(50, 200))); 19 | Debug.Assert(space.Intersects(new SpaceRange(190, 10))); 20 | 21 | Debug.Assert(space.Intersects(new SpaceRange(90, 20))); 22 | Debug.Assert(space.Intersects(new SpaceRange(190, 20))); 23 | 24 | Debug.Assert(!space.Intersects(new SpaceRange(50, 25))); 25 | Debug.Assert(!space.Intersects(new SpaceRange(90, 10))); 26 | Debug.Assert(!space.Intersects(new SpaceRange(200, 10))); 27 | } 28 | #endif 29 | 30 | public RangeMap() 31 | { 32 | } 33 | 34 | public void AddRange(ulong offset, ulong range) 35 | { 36 | ValidateParams(offset, range); 37 | 38 | if (mTree.Count == 0) 39 | { 40 | mTree.Add(new SpaceRange(offset, range)); 41 | return; 42 | } 43 | 44 | var newRange = new SpaceRange(offset, range); 45 | var near = mTree.FindNearestValues(newRange); 46 | 47 | //try to merge with the left 48 | if (near.Item1.IsValid) 49 | { 50 | if (near.Item1.Intersects(newRange)) 51 | throw new Exception("Range already added"); 52 | if (near.Item1.Offset + near.Item1.Range == newRange.Offset) 53 | { 54 | if (!mTree.Remove(near.Item1)) 55 | throw new Exception("Failed to remove node, this should not happen."); 56 | newRange = new SpaceRange(near.Item1.Offset, near.Item1.Range + newRange.Range); 57 | } 58 | } 59 | 60 | //try to merge with the right 61 | if (near.Item2.IsValid) 62 | { 63 | if (near.Item2.Intersects(newRange)) 64 | throw new Exception("Range already added."); 65 | if (newRange.Offset + newRange.Range == near.Item2.Offset) 66 | { 67 | if (!mTree.Remove(near.Item2)) 68 | throw new Exception("Failed to remove node, this should not happen."); 69 | newRange = new SpaceRange(newRange.Offset, newRange.Range + near.Item2.Range); 70 | } 71 | } 72 | 73 | mTree.Add(newRange); 74 | } 75 | 76 | public void RemoveRange(ulong offset, ulong range) 77 | { 78 | ValidateParams(offset, range); 79 | 80 | if (mTree.Count == 0) 81 | throw new Exception("Empty tree."); 82 | 83 | var removeRange = new SpaceRange(offset, range); 84 | 85 | var near = mTree.FindNearestValues(removeRange); 86 | 87 | if (near.Item1.IsValid && near.Item1.Contains(removeRange)) 88 | { 89 | SplitNode(near.Item1, removeRange); 90 | return; 91 | } 92 | else if (near.Item2.IsValid && near.Item2.Contains(removeRange)) 93 | { 94 | SplitNode(near.Item2, removeRange); 95 | return; 96 | } 97 | 98 | throw new Exception("Could not find range to remove."); 99 | } 100 | 101 | private void SplitNode(SpaceRange existingRange, SpaceRange removeRange) 102 | { 103 | var newLowerRange = new SpaceRange(existingRange.Offset, removeRange.Offset - existingRange.Offset); 104 | var newUpperRange = new SpaceRange(removeRange.Offset + removeRange.Range, existingRange.Range - newLowerRange.Range - removeRange.Range); 105 | if (!mTree.Remove(existingRange)) 106 | throw new Exception("Failed to remove range."); 107 | if (newLowerRange.Range != 0) 108 | mTree.Add(newLowerRange); 109 | if (newUpperRange.Range != 0) 110 | mTree.Add(newUpperRange); 111 | } 112 | 113 | void ValidateParams(ulong offset, ulong range) 114 | { 115 | if (range == 0) 116 | throw new ArgumentOutOfRangeException("range", "Range cannot be zero."); 117 | if (offset > (offset + range)) 118 | throw new OverflowException(); 119 | } 120 | 121 | public bool ContainsRange(ulong offset, ulong range) 122 | { 123 | if (mTree.Count == 0) 124 | return false; 125 | var search = new SpaceRange(offset, range); 126 | var near = mTree.FindNearestValues(search); 127 | return (near.Item1.IsValid && near.Item1.Contains(search)) || (near.Item2.IsValid && near.Item2.Contains(search)); 128 | } 129 | 130 | public void Print() 131 | { 132 | foreach (var sr in mTree.GetInorderEnumerator()) 133 | { 134 | Console.WriteLine(sr); 135 | } 136 | Console.WriteLine(); 137 | } 138 | 139 | struct SpaceRange : IComparable 140 | { 141 | public readonly ulong Offset; 142 | public readonly ulong Range; 143 | 144 | public SpaceRange(ulong offset, ulong range) 145 | { 146 | this.Offset = offset; 147 | this.Range = range; 148 | } 149 | 150 | public bool IsValid => Range != 0; 151 | 152 | public int CompareTo(SpaceRange other) 153 | { 154 | if (this.Offset < other.Offset) 155 | return -1; 156 | if (this.Offset > other.Offset) 157 | return 1; 158 | return 0; 159 | } 160 | 161 | public bool Intersects(SpaceRange other) 162 | { 163 | ulong myEnd = this.Offset + this.Range; 164 | if (other.Offset >= this.Offset && other.Offset < myEnd) 165 | return true; 166 | ulong otherEnd = other.Offset + other.Range; 167 | if (otherEnd > this.Offset && otherEnd <= myEnd) 168 | return true; 169 | 170 | if (other.Offset < this.Offset && otherEnd > myEnd) 171 | return true; 172 | 173 | return false; 174 | } 175 | 176 | public bool Contains(SpaceRange other) 177 | { 178 | ulong myEnd = this.Offset + this.Range; 179 | ulong otherEnd = other.Offset + other.Range; 180 | return other.Offset >= this.Offset && otherEnd <= myEnd; 181 | } 182 | 183 | public override string ToString() 184 | { 185 | return string.Format("{{ {0:x}, {1:x} }}", Offset, Range); 186 | } 187 | } 188 | } 189 | 190 | 191 | } 192 | -------------------------------------------------------------------------------- /ZfsSharpLib/Structs.Ddt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace ZfsSharpLib 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | struct ddt_key_t 8 | { 9 | public zio_cksum_t cksum; 10 | ulong prop; 11 | 12 | public zio_compress Compress 13 | { 14 | get { return (zio_compress)((prop >> 32) & 0xff); } 15 | } 16 | 17 | public ushort PSize 18 | { 19 | get { return (ushort)((prop >> 16) & 0xffff); } 20 | } 21 | 22 | public ushort LSize 23 | { 24 | get { return (ushort)(prop & 0xffff); } 25 | } 26 | } 27 | 28 | [StructLayout(LayoutKind.Sequential)] 29 | unsafe struct ddt_phys_t 30 | { 31 | public dva_t dva1; 32 | public dva_t dva2; 33 | public dva_t dva3; 34 | public UInt64 refcnt; 35 | public UInt64 phys_birth; 36 | } 37 | 38 | enum ddt_type 39 | { 40 | DDT_TYPE_ZAP = 0, 41 | DDT_TYPES 42 | } 43 | 44 | enum ddt_phys_type 45 | { 46 | DDT_PHYS_DITTO = 0, 47 | DDT_PHYS_SINGLE = 1, 48 | DDT_PHYS_DOUBLE = 2, 49 | DDT_PHYS_TRIPLE = 3, 50 | DDT_PHYS_TYPES 51 | } 52 | 53 | enum ddt_class 54 | { 55 | DDT_CLASS_DITTO = 0, 56 | DDT_CLASS_DUPLICATE, 57 | DDT_CLASS_UNIQUE, 58 | DDT_CLASSES 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ZfsSharpLib/Structs.Dsl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace ZfsSharpLib 5 | { 6 | [Flags] 7 | enum DD_FLAG : ulong 8 | { 9 | None = 0, 10 | USED_BREAKDOWN = 1 << 0 11 | } 12 | 13 | enum dd_used_t 14 | { 15 | DD_USED_HEAD, 16 | DD_USED_SNAP, 17 | DD_USED_CHILD, 18 | DD_USED_CHILD_RSRV, 19 | DD_USED_REFRSRV, 20 | //DD_USED_NUM 21 | } 22 | 23 | [StructLayout(LayoutKind.Sequential)] 24 | unsafe struct dsl_dir_phys_t 25 | { 26 | const int DD_USED_NUM = 0x00000005; 27 | 28 | ulong creation_time; /* not actually used */ 29 | public long head_dataset_obj; 30 | public long parent_obj; 31 | public long origin_obj; 32 | public long child_dir_zapobj; 33 | /* 34 | * how much space our children are accounting for; for leaf 35 | * datasets, == physical space used by fs + snaps 36 | */ 37 | public ulong used_bytes; 38 | public ulong compressed_bytes; 39 | public ulong uncompressed_bytes; 40 | /* Administrative quota setting */ 41 | public ulong quota; 42 | /* Administrative reservation setting */ 43 | public ulong reserved; 44 | public long props_zapobj; 45 | public ulong deleg_zapobj; /* dataset delegation permissions */ 46 | public DD_FLAG flags; 47 | public fixed ulong used_breakdown[DD_USED_NUM]; 48 | public long clones; /* dsl_dir objects */ 49 | fixed ulong pad[13]; /* pad out to 256 bytes for good measure */ 50 | } 51 | 52 | [Flags] 53 | enum DS_FLAG : ulong 54 | { 55 | None = 0, 56 | INCONSISTENT = (1UL << 0), 57 | /// 58 | /// Do not allow this dataset to be promoted. 59 | /// 60 | NOPROMOTE = (1UL << 1), 61 | /// 62 | /// UNIQUE_ACCURATE is set if ds_unique_bytes has been correctly 63 | /// calculated for head datasets (starting with SPA_VERSION_UNIQUE_ACCURATE, 64 | /// refquota/refreservations). 65 | /// 66 | UNIQUE_ACCURATE = (1UL << 2), 67 | /// 68 | /// DEFER_DESTROY is set after 'zfs destroy -d' has been called 69 | /// on a dataset. This allows the dataset to be destroyed using 'zfs release'. 70 | /// 71 | DEFER_DESTROY = (1UL << 3), 72 | /// 73 | /// CI_DATASET is set if the dataset contains a file system whose 74 | /// name lookups should be performed case-insensitively. 75 | /// 76 | CI_DATASET = (1UL << 16), 77 | } 78 | 79 | [StructLayout(LayoutKind.Sequential)] 80 | unsafe struct dsl_dataset_phys_t 81 | { 82 | public long dir_obj; /* DMU_OT_DSL_DIR */ 83 | public long prev_snap_obj; /* DMU_OT_DSL_DATASET */ 84 | public ulong prev_snap_txg; 85 | public long next_snap_obj; /* DMU_OT_DSL_DATASET */ 86 | public long snapnames_zapobj; /* DMU_OT_DSL_SNAP_MAP 0 for snaps */ 87 | public ulong num_children; /* clone/snap children; ==0 for head */ 88 | public ulong creation_time; /* seconds since 1970 */ 89 | public ulong creation_txg; 90 | public long deadlist_obj; /* DMU_OT_DEADLIST */ 91 | /* 92 | * referenced_bytes, compressed_bytes, and uncompressed_bytes 93 | * include all blocks referenced by this dataset, including those 94 | * shared with any other datasets. 95 | */ 96 | public ulong referenced_bytes; 97 | public ulong compressed_bytes; 98 | public ulong uncompressed_bytes; 99 | public ulong unique_bytes; /* only relevant to snapshots */ 100 | /* 101 | * The fsid_guid is a 56-bit ID that can change to avoid 102 | * collisions. The guid is a 64-bit ID that will never 103 | * change, so there is a small probability that it will collide. 104 | */ 105 | public ulong fsid_guid; 106 | public ulong guid; 107 | public DS_FLAG flags; 108 | public blkptr_t bp; 109 | public long next_clones_obj; /* DMU_OT_DSL_CLONES */ 110 | public long props_obj; /* DMU_OT_DSL_PROPS for snaps */ 111 | public long userrefs_obj; /* DMU_OT_USERREFS */ 112 | fixed ulong pad[5]; /* pad out to 320 bytes for good measure */ 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /ZfsSharpLib/Structs.SpaceMaps.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace ZfsSharpLib 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | struct space_map_obj 7 | { 8 | public long smo_object; /* on-disk space map object */ 9 | public long smo_objsize; /* size of the object */ 10 | public long smo_alloc; /* space allocated from the map */ 11 | } 12 | 13 | enum SpaceMapEntryType 14 | { 15 | A = 0, 16 | F = 1, 17 | } 18 | 19 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 20 | struct spaceMapEntry 21 | { 22 | ulong mData; 23 | 24 | public spaceMapEntry(SpaceMapEntryType type, ulong offset, ulong run) 25 | { 26 | mData = 0; 27 | } 28 | 29 | public bool IsDebug 30 | { 31 | get 32 | { 33 | return (mData >> 63) == 1; 34 | } 35 | } 36 | 37 | // non-debug fields 38 | 39 | public ulong Offset 40 | { 41 | get 42 | { 43 | ulong mask = ~0UL >> 1; 44 | return (mData & mask) >> 16; 45 | } 46 | } 47 | 48 | public SpaceMapEntryType Type 49 | { 50 | get 51 | { 52 | return (SpaceMapEntryType)((mData >> 15) & 1); 53 | } 54 | } 55 | 56 | public ulong Run 57 | { 58 | get 59 | { 60 | return (mData & 0x7fff) + 1; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ZfsSharpLib/Structs.Zpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace ZfsSharpLib 5 | { 6 | public enum ZfsItemType 7 | { 8 | None = 0, 9 | /// 10 | /// Fifo 11 | /// 12 | S_IFIFO = 0x1, 13 | /// 14 | /// Character Special Device 15 | /// 16 | S_IFCHR = 0x2, 17 | /// 18 | /// Directory 19 | /// 20 | S_IFDIR = 0x4, 21 | /// 22 | /// Block special device 23 | /// 24 | S_IFBLK = 0x6, 25 | /// 26 | /// Regular file 27 | /// 28 | S_IFREG = 0x8, 29 | /// 30 | /// Symbolic Link 31 | /// 32 | S_IFLNK = 0xA, 33 | /// 34 | /// Socket 35 | /// 36 | S_IFSOCK = 0xC, 37 | /// 38 | /// Door 39 | /// 40 | S_IFDOOR = 0xD, 41 | /// 42 | /// Event Port 43 | /// 44 | S_IFPORT = 0xE, 45 | } 46 | 47 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 48 | unsafe struct sa_hdr_phys_t 49 | { 50 | const uint SA_MAGIC = 0x2F505A; 51 | 52 | uint sa_magic; 53 | ushort sa_layout_info; /* Encoded with hdrsize and layout number */ 54 | public fixed ushort sa_lengths[1]; /* optional sizes for variable length attrs */ 55 | /* ... Data follows the lengths. */ 56 | 57 | public int hdrsz 58 | { 59 | get { return (sa_layout_info >> 10) * 8; } 60 | } 61 | public int layout 62 | { 63 | get { return sa_layout_info & 0x3FF; } 64 | } 65 | 66 | public void VerifyMagic() 67 | { 68 | if (sa_magic != SA_MAGIC) 69 | throw new Exception(); 70 | } 71 | } 72 | enum zpl_attr_t : short 73 | { 74 | ZPL_ATIME = 0, 75 | ZPL_MTIME, 76 | ZPL_CTIME, 77 | ZPL_CRTIME, 78 | ZPL_GEN, 79 | ZPL_MODE, 80 | ZPL_SIZE, 81 | ZPL_PARENT, 82 | ZPL_LINKS, 83 | ZPL_XATTR, 84 | ZPL_RDEV, 85 | ZPL_FLAGS, 86 | ZPL_UID, 87 | ZPL_GID, 88 | ZPL_PAD, 89 | ZPL_ZNODE_ACL, 90 | ZPL_DACL_COUNT, 91 | ZPL_SYMLINK, 92 | ZPL_SCANSTAMP, 93 | ZPL_DACL_ACES, 94 | //ZPL_END 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /ZfsSharpLib/VirtualDevices/HddVdev.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.IO; 6 | using ZfsSharpLib.HardDisks; 7 | 8 | namespace ZfsSharpLib.VirtualDevices 9 | { 10 | class HddVdev : Vdev 11 | { 12 | readonly HardDisk mHdd; 13 | public HddVdev(NvList config, LeafVdevInfo hdd) 14 | : base(config) 15 | { 16 | this.mHdd = hdd.HDD; 17 | } 18 | 19 | protected override void ReadBytesCore(Span dest, long offset) 20 | { 21 | mHdd.ReadBytes(dest, offset); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ZfsSharpLib/VirtualDevices/MirrorVdev.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ZfsSharpLib.VirtualDevices 7 | { 8 | class MirrorVdev : Vdev 9 | { 10 | readonly Vdev[] mVdevs; 11 | public MirrorVdev(NvList config, Dictionary leafs) 12 | : base(config) 13 | { 14 | this.mVdevs = config.Get("children") 15 | .Select(child => Vdev.Create(child, leafs)) 16 | .ToArray(); 17 | } 18 | 19 | protected override void ReadBytesCore(Span dest, long offset) 20 | { 21 | //TODO: use more than one child 22 | mVdevs[0].ReadBytes(dest, offset); 23 | } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ZfsSharpLib/VirtualDevices/RaidzVdev.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | 8 | namespace ZfsSharpLib.VirtualDevices 9 | { 10 | class RaidzVdev : Vdev 11 | { 12 | readonly Vdev[] mVdevs; 13 | readonly ulong mNparity; 14 | readonly int mUnitShift; //ashift 15 | public RaidzVdev(NvList config, Dictionary leafs) 16 | : base(config) 17 | { 18 | this.mVdevs = config.Get("children") 19 | .Select(child => Vdev.Create(child, leafs)) 20 | .OrderBy(child => child.ID) 21 | .ToArray(); 22 | 23 | mNparity = config.Get("nparity"); 24 | mUnitShift = (int)config.Get("ashift"); 25 | } 26 | 27 | protected override void ReadBytesCore(Span dest, long offset) 28 | { 29 | var rm = vdev_raidz_map_alloc((ulong)dest.Length, (ulong)offset, mUnitShift, (ulong)mVdevs.Length, mNparity); 30 | int ptr = 0; 31 | for (ulong i = rm.rm_firstdatacol; i < rm.rm_cols; i++) 32 | { 33 | var col = rm.rm_col[i]; 34 | if (col.rc_size > int.MaxValue) 35 | throw new NotSupportedException("RaidZ column too big."); 36 | mVdevs[col.rc_devidx].ReadBytes(dest.Slice(0, (int)col.rc_size), (long)col.rc_offset); 37 | ptr += (int)col.rc_size; 38 | } 39 | } 40 | 41 | //All below is copy-pasted with some modification from usr\src\uts\common\fs\zfs\vdev_raidz.c 42 | //Inspired by http://www.joyent.com/blog/zfs-raidz-striping 43 | 44 | [StructLayout(LayoutKind.Sequential)] 45 | struct raidz_col 46 | { 47 | public ulong rc_devidx; /* child device index for I/O */ 48 | public ulong rc_offset; /* device offset */ 49 | public ulong rc_size; /* I/O size */ 50 | //public void* rc_data; /* I/O data */ 51 | //public void* rc_gdata; /* used to store the "good" version */ 52 | public int rc_error; /* I/O error for this device */ 53 | public byte rc_tried; /* Did we attempt this I/O column? */ 54 | public byte rc_skipped; /* Did we skip this I/O column? */ 55 | } 56 | 57 | [StructLayout(LayoutKind.Sequential)] 58 | struct raidz_map 59 | { 60 | public ulong rm_cols; /* Regular column count */ 61 | public ulong rm_scols; /* Count including skipped columns */ 62 | public ulong rm_bigcols; /* Number of oversized columns */ 63 | public ulong rm_asize; /* Actual total I/O size */ 64 | public ulong rm_missingdata; /* Count of missing data devices */ 65 | public ulong rm_missingparity; /* Count of missing parity devices */ 66 | public ulong rm_firstdatacol; /* First data column/parity count */ 67 | public ulong rm_nskip; /* Skipped sectors for padding */ 68 | public ulong rm_skipstart; /* Column index of padding start */ 69 | //public void* rm_datacopy; /* rm_asize-buffer of copied data */ 70 | public UIntPtr rm_reports; /* # of referencing checksum reports */ 71 | public byte rm_freed; /* map no longer has referencing ZIO */ 72 | public byte rm_ecksuminjected; /* checksum error was injected */ 73 | public raidz_col[] rm_col; /* Flexible array of I/O columns */ 74 | } 75 | 76 | static ulong roundup(ulong x, ulong y) 77 | { 78 | return ((((x) + ((y) - 1)) / (y)) * (y)); 79 | } 80 | 81 | static raidz_map vdev_raidz_map_alloc(ulong size, ulong offset, int unit_shift, ulong dcols, ulong nparity) 82 | { 83 | raidz_map rm; 84 | ulong b = offset >> unit_shift; 85 | ulong s = size >> unit_shift; 86 | ulong f = b % dcols; 87 | ulong o = (b / dcols) << unit_shift; 88 | ulong q, r, c, bc, col, acols, scols, coff, devidx, asize, tot; 89 | 90 | q = s / (dcols - nparity); 91 | r = s - q * (dcols - nparity); 92 | bc = (r == 0 ? 0 : r + nparity); 93 | tot = s + nparity * (q + (ulong)(r == 0 ? 0 : 1)); 94 | 95 | if (q == 0) 96 | { 97 | acols = bc; 98 | scols = Math.Min(dcols, roundup(bc, nparity + 1)); 99 | } 100 | else 101 | { 102 | acols = dcols; 103 | scols = dcols; 104 | } 105 | 106 | 107 | Debug.Assert(acols <= scols); 108 | 109 | rm = default(raidz_map); 110 | rm.rm_col = new raidz_col[scols]; 111 | 112 | rm.rm_cols = acols; 113 | rm.rm_scols = scols; 114 | rm.rm_bigcols = bc; 115 | rm.rm_skipstart = bc; 116 | rm.rm_missingdata = 0; 117 | rm.rm_missingparity = 0; 118 | rm.rm_firstdatacol = nparity; 119 | //rm.rm_datacopy = (void*)0; 120 | rm.rm_reports = UIntPtr.Zero; 121 | rm.rm_freed = 0; 122 | rm.rm_ecksuminjected = 0; 123 | 124 | asize = 0; 125 | 126 | for (c = 0; c < scols; c++) 127 | { 128 | col = f + c; 129 | coff = o; 130 | if (col >= dcols) 131 | { 132 | col -= dcols; 133 | coff += 1UL << unit_shift; 134 | } 135 | rm.rm_col[c].rc_devidx = col; 136 | rm.rm_col[c].rc_offset = coff; 137 | //rm.rm_col[c].rc_data = NULL; 138 | //rm.rm_col[c].rc_gdata = NULL; 139 | rm.rm_col[c].rc_error = 0; 140 | rm.rm_col[c].rc_tried = 0; 141 | rm.rm_col[c].rc_skipped = 0; 142 | 143 | if (c >= acols) 144 | rm.rm_col[c].rc_size = 0; 145 | else if (c < bc) 146 | rm.rm_col[c].rc_size = (q + 1) << unit_shift; 147 | else 148 | rm.rm_col[c].rc_size = q << unit_shift; 149 | 150 | asize += rm.rm_col[c].rc_size; 151 | } 152 | 153 | Debug.Assert(asize == (tot << unit_shift)); 154 | rm.rm_asize = roundup(asize, (nparity + 1) << unit_shift); 155 | rm.rm_nskip = roundup(tot, nparity + 1) - tot; 156 | Debug.Assert((rm.rm_asize - asize) == (rm.rm_nskip << unit_shift)); 157 | Debug.Assert(rm.rm_nskip <= nparity); 158 | 159 | /* 160 | * If all data stored spans all columns, there's a danger that parity 161 | * will always be on the same device and, since parity isn't read 162 | * during normal operation, that that device's I/O bandwidth won't be 163 | * used effectively. We therefore switch the parity every 1MB. 164 | * 165 | * ... at least that was, ostensibly, the theory. As a practical 166 | * matter unless we juggle the parity between all devices evenly, we 167 | * won't see any benefit. Further, occasional writes that aren't a 168 | * multiple of the LCM of the number of children and the minimum 169 | * stripe width are sufficient to avoid pessimal behavior. 170 | * Unfortunately, this decision created an implicit on-disk format 171 | * requirement that we need to support for all eternity, but only 172 | * for single-parity RAID-Z. 173 | * 174 | * If we intend to skip a sector in the zeroth column for padding 175 | * we must make sure to note this swap. We will never intend to 176 | * skip the first column since at least one data and one parity 177 | * column must appear in each row. 178 | */ 179 | Debug.Assert(rm.rm_cols >= 2); 180 | Debug.Assert(rm.rm_col[0].rc_size == rm.rm_col[1].rc_size); 181 | 182 | if (rm.rm_firstdatacol == 1 && (offset & (1UL << 20)) != 0) 183 | { 184 | devidx = rm.rm_col[0].rc_devidx; 185 | o = rm.rm_col[0].rc_offset; 186 | rm.rm_col[0].rc_devidx = rm.rm_col[1].rc_devidx; 187 | rm.rm_col[0].rc_offset = rm.rm_col[1].rc_offset; 188 | rm.rm_col[1].rc_devidx = devidx; 189 | rm.rm_col[1].rc_offset = o; 190 | 191 | if (rm.rm_skipstart == 0) 192 | rm.rm_skipstart = 1; 193 | } 194 | 195 | return (rm); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /ZfsSharpLib/VirtualDevices/Vdev.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ZfsSharpLib.VirtualDevices 7 | { 8 | abstract class Vdev 9 | { 10 | private MetaSlabs mMetaSlabs = null; 11 | 12 | protected Vdev(NvList config) 13 | { 14 | this.Guid = config.Get("guid"); 15 | this.ID = config.Get("id"); 16 | 17 | MetaSlabArray = config.GetOptional("metaslab_array"); 18 | MetaSlabShift = config.GetOptional("metaslab_shift"); 19 | AShift = config.GetOptional("ashift"); 20 | ASize = config.GetOptional("asize"); 21 | } 22 | 23 | //a bit of a layering violation 24 | public void InitMetaSlabs(ObjectSet mos) 25 | { 26 | mMetaSlabs = new MetaSlabs(mos, (long)MetaSlabArray.Value, (int)MetaSlabShift.Value, (int)AShift.Value); 27 | } 28 | 29 | public void ReadBytes(Span dest, long offset) 30 | { 31 | if (mMetaSlabs != null && !mMetaSlabs.ContainsRange(offset, dest.Length)) 32 | { 33 | throw new Exception("Reading unallocated data."); 34 | } 35 | ReadBytesCore(dest, offset); 36 | } 37 | 38 | protected abstract void ReadBytesCore(Span dest, long offset); 39 | 40 | public ulong Guid 41 | { 42 | get; 43 | private set; 44 | } 45 | public ulong ID { get; private set; } 46 | 47 | //These are optional, as children of raidz and mirror don't have them. 48 | public ulong? MetaSlabArray { get; private set; } 49 | public ulong? MetaSlabShift { get; private set; } 50 | public ulong? AShift { get; private set; } 51 | public ulong? ASize { get; private set; } 52 | 53 | public static Vdev Create(NvList config, Dictionary leafs) 54 | { 55 | var type = config.Get("type"); 56 | switch (type) 57 | { 58 | case "mirror": 59 | return new MirrorVdev(config, leafs); 60 | case "disk": 61 | case "file": 62 | return new HddVdev(config, leafs[config.Get("guid")]); 63 | case "raidz": 64 | return new RaidzVdev(config, leafs); 65 | default: 66 | throw new NotSupportedException("Unknown type: " + type); 67 | } 68 | throw new NotImplementedException(); 69 | } 70 | 71 | public static Vdev[] CreateVdevTree(List hdds) 72 | { 73 | var hddMap = new Dictionary(); 74 | var innerVdevConfigs = new Dictionary(); 75 | foreach (var hdd in hdds) 76 | { 77 | hddMap.Add(hdd.Config.Get("guid"), hdd); 78 | var vdevTree = hdd.Config.Get("vdev_tree"); 79 | innerVdevConfigs[vdevTree.Get("guid")] = vdevTree; 80 | } 81 | 82 | var innerVdevs = new List(); 83 | foreach (var kvp in innerVdevConfigs) 84 | { 85 | innerVdevs.Add(Vdev.Create(kvp.Value, hddMap)); 86 | } 87 | 88 | ulong calculatedTopGuid = 0; 89 | for (int i = 0; i < innerVdevs.Count; i++) 90 | { 91 | calculatedTopGuid += innerVdevs[i].Guid; 92 | } 93 | 94 | var ret = innerVdevs.OrderBy(v => v.ID).ToArray(); 95 | for (uint i = 0; i < ret.Length; i++) 96 | { 97 | if (ret[i].ID != i) 98 | throw new Exception("Missing vdev."); 99 | } 100 | return ret; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /ZfsSharpLib/Zfs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | using ZfsSharpLib.VirtualDevices; 7 | 8 | namespace ZfsSharpLib 9 | { 10 | public class Zfs : IDisposable 11 | { 12 | const string ROOT_DATASET = "root_dataset"; 13 | const string CONFIG = "config"; 14 | const string DDT_STATISTICS = "DDT-statistics"; 15 | const ulong SUPPORTED_VERSION = 5000; 16 | 17 | readonly static ReadOnlyCollection sSupportReadFeatures = new ReadOnlyCollection(new string[]{ 18 | "com.delphix:hole_birth", //as far as I can tell this just means that hole block pointers will have their birth fields filled in 19 | "com.delphix:extensible_dataset", //this means a DSL_DATASET DN contains ZAP entries 20 | "org.illumos:lz4_compress", 21 | "com.delphix:embedded_data", 22 | }); 23 | 24 | List mHdds; 25 | Zio mZio; 26 | Dictionary mObjDir; 27 | NvList mConfig; 28 | ObjectSet mMos; 29 | 30 | static void assertStructSize(int size) 31 | { 32 | int messuredSize = Program.SizeOf(); 33 | System.Diagnostics.Debug.Assert(messuredSize == size); 34 | } 35 | 36 | /// 37 | /// Either the path to a virutal hard disk image file or a directory containing multiple images. 38 | public Zfs(string directoryOrFile) 39 | { 40 | //make sure we correctly set the size of structs 41 | assertStructSize(zio_gbh_phys_t.SPA_GANGBLOCKSIZE); 42 | 43 | mHdds = LeafVdevInfo.GetLeafVdevs(directoryOrFile); 44 | 45 | try 46 | { 47 | Load(); 48 | } 49 | catch 50 | { 51 | foreach (var hdd in mHdds) 52 | { 53 | hdd.Dispose(); 54 | } 55 | throw; 56 | } 57 | } 58 | 59 | private void Load() 60 | { 61 | if (mHdds.Count == 0) 62 | throw new Exception("Did not find any hard drives."); 63 | 64 | //make sure we support reading the pool 65 | foreach (var hdd in mHdds) 66 | { 67 | CheckVersion(hdd.Config); 68 | } 69 | 70 | //ensure all HDDs are part of the same pool 71 | var poolGuid = mHdds.Select(h => h.Config.Get("pool_guid")).Distinct().ToArray(); 72 | if (poolGuid.Length != 1) 73 | throw new Exception("Hard drives are part of different pools: " + string.Join(", ", poolGuid.Select(p => p.ToString()))); 74 | 75 | //ensure all HDDs agree on the transaction group id 76 | var txg = mHdds.Select(hdd => hdd.Uberblock.Txg).Distinct().ToArray(); 77 | if (txg.Length != 1) 78 | throw new Exception("Uberblocks do not all have the same transaction group id: " + string.Join(", ", txg.Select(p => p.ToString()))); 79 | 80 | var ub = mHdds[0].Uberblock; 81 | if (ub.Txg == 0) 82 | throw new Exception("Root block pointer's transaction group is zero!"); 83 | 84 | var vdevs = Vdev.CreateVdevTree(mHdds); 85 | 86 | mZio = new Zio(vdevs); 87 | 88 | mMos = new ObjectSet(mZio, mZio.Get(ub.rootbp)); 89 | if (mMos.Type != dmu_objset_type_t.DMU_OST_META) 90 | throw new Exception("Given block pointer did not point to the MOS."); 91 | 92 | mZio.InitMetaSlabs(mMos); 93 | //the second time we will make sure that space maps contain themselves 94 | mZio.InitMetaSlabs(mMos); 95 | 96 | var objectDirectory = mMos.ReadEntry(1); 97 | //The MOS's directory sometimes has things that don't like like directory entries. 98 | //For example, the "scan" entry has scrub status stuffed into as an array of longs. 99 | mObjDir = Zap.GetDirectoryEntries(objectDirectory, true); 100 | 101 | if (mObjDir.ContainsKey(DDT_STATISTICS)) 102 | { 103 | var ddtStats = Zap.Parse(mMos.ReadEntry(mObjDir[DDT_STATISTICS])); 104 | //TODO: maybe do something interesting with the stats 105 | } 106 | 107 | { 108 | var configDn = mMos.ReadEntry(mObjDir[CONFIG]); 109 | var configBytes = Program.RentBytes(checked((int)configDn.AvailableDataSize)); 110 | configDn.Read(configBytes, 0); 111 | mConfig = new NvList(configBytes); 112 | Program.ReturnBytes(configBytes); 113 | } 114 | 115 | CheckVersion(mConfig); 116 | CheckFeatures(); 117 | } 118 | 119 | private void CheckFeatures() 120 | { 121 | var fr = Zap.GetDirectoryEntries(mMos, mObjDir["features_for_read"]); 122 | var fw = Zap.GetDirectoryEntries(mMos, mObjDir["features_for_write"]); 123 | var ff = Zap.Parse(mMos.ReadEntry(mObjDir["feature_descriptions"])).ToDictionary(kvp => kvp.Key, kvp => Encoding.ASCII.GetString((byte[])kvp.Value)); 124 | if (fw.ContainsKey("com.delphix:enabled_txg") && fw["com.delphix:enabled_txg"] > 0) 125 | { 126 | var fe = Zap.GetDirectoryEntries(mMos, mObjDir["feature_enabled_txg"]); 127 | } 128 | 129 | foreach (var feature in fr) 130 | { 131 | if (feature.Value != 0 && !sSupportReadFeatures.Contains(feature.Key)) 132 | { 133 | throw new Exception("Unsupported feature: " + feature.Key); 134 | } 135 | } 136 | } 137 | 138 | private static void CheckVersion(NvList cfg) 139 | { 140 | if (cfg.Get("version") != SUPPORTED_VERSION) 141 | throw new Exception("Unsupported version."); 142 | 143 | var state = (pool_state)cfg.Get("state"); 144 | if (state != pool_state.ACTIVE && state != pool_state.EXPORTED) 145 | throw new Exception("Unknown state: " + state); 146 | 147 | var features = cfg.Get("features_for_read"); 148 | foreach (var kvp in features) 149 | { 150 | if (!sSupportReadFeatures.Contains(kvp.Key)) 151 | throw new Exception("Unsupported feature: " + kvp.Key); 152 | } 153 | } 154 | 155 | public DatasetDirectory GetRootDataset() 156 | { 157 | var dsd = new DatasetDirectory(mMos, mObjDir[ROOT_DATASET], mConfig.Get("name"), mZio); 158 | return dsd; 159 | } 160 | 161 | public List GetAllDataSets() 162 | { 163 | var ret = new List(); 164 | listDataSetName(mObjDir[ROOT_DATASET], mConfig.Get("name"), ret); 165 | return ret; 166 | } 167 | 168 | void listDataSetName(long objectId, string nameBase, List ret) 169 | { 170 | var dsd = new DatasetDirectory(mMos, objectId, nameBase, mZio); 171 | ret.Add(dsd); 172 | 173 | foreach (var kvp in dsd.GetChildIds()) 174 | { 175 | listDataSetName(kvp.Value, nameBase + "/" + kvp.Key, ret); 176 | } 177 | } 178 | 179 | public void Dispose() 180 | { 181 | foreach (var hdd in mHdds) 182 | { 183 | hdd.Dispose(); 184 | } 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /ZfsSharpLib/ZfsSharpLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7.3 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ZfsSharpLib/lz4net/LZ4.portable/LZ4Codec.portable.cs: -------------------------------------------------------------------------------- 1 | #region license 2 | 3 | /* 4 | Copyright (c) 2013, Milosz Krajewski 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided 8 | that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this list of conditions 11 | and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions 14 | and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 23 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #endregion 27 | 28 | using LZ4.Services; 29 | using System.Runtime.CompilerServices; 30 | 31 | namespace LZ4 32 | { 33 | public static partial class LZ4Codec 34 | { 35 | /// Determines whether VS2015 runtime is installed. 36 | /// Note, on Mono the Registry class is not available at all, 37 | /// so access to it have to be isolated. 38 | /// true it VS2015 runtime is installed, false otherwise. 39 | private static bool Has2015Runtime() { return false; } 40 | 41 | // ReSharper disable InconsistentNaming 42 | 43 | /// Initializes codecs from LZ4s. 44 | [MethodImpl(MethodImplOptions.NoInlining)] 45 | private static void InitializeLZ4s() 46 | { 47 | _service_S32 = new Safe32LZ4Service(); 48 | _service_S64 = new Safe64LZ4Service(); 49 | } 50 | 51 | // ReSharper restore InconsistentNaming 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ZfsSharpLib/lz4net/LZ4/ILZ4Service.cs: -------------------------------------------------------------------------------- 1 | #region license 2 | 3 | /* 4 | Copyright (c) 2013, Milosz Krajewski 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided 8 | that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this list of conditions 11 | and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions 14 | and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 23 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #endregion 27 | 28 | namespace LZ4 29 | { 30 | internal interface ILZ4Service 31 | { 32 | string CodecName { get; } 33 | int Decode(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int outputLength, bool knownOutputLength); 34 | } 35 | } -------------------------------------------------------------------------------- /ZfsSharpLib/lz4net/LZ4/LZ4Codec.cs: -------------------------------------------------------------------------------- 1 | #region license 2 | 3 | /* 4 | Copyright (c) 2013-2017, Milosz Krajewski 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided 8 | that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this list of conditions 11 | and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions 14 | and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 23 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #endregion 27 | 28 | using System; 29 | using System.Diagnostics; 30 | using System.Runtime.CompilerServices; 31 | using System.Text; 32 | 33 | namespace LZ4 34 | { 35 | /// 36 | /// LZ4 codec selecting best implementation depending on platform. 37 | /// 38 | public static partial class LZ4Codec 39 | { 40 | #region fields 41 | 42 | /// Encoding service. 43 | private static readonly ILZ4Service Encoder; 44 | 45 | /// Encoding service for HC algorithm. 46 | private static readonly ILZ4Service EncoderHC; 47 | 48 | /// Decoding service. 49 | private static readonly ILZ4Service Decoder; 50 | 51 | // ReSharper disable InconsistentNaming 52 | 53 | // safe c# 54 | private static ILZ4Service _service_S32; 55 | private static ILZ4Service _service_S64; 56 | 57 | // ReSharper restore InconsistentNaming 58 | 59 | #endregion 60 | 61 | #region initialization 62 | 63 | /// Initializes the class. 64 | static LZ4Codec() 65 | { 66 | InitializeLZ4s(); 67 | 68 | ILZ4Service encoder, decoder, encoderHC; 69 | SelectCodec(out encoder, out decoder, out encoderHC); 70 | 71 | Encoder = encoder; 72 | Decoder = decoder; 73 | EncoderHC = encoderHC; 74 | 75 | if (Encoder == null || Decoder == null) 76 | { 77 | throw new NotSupportedException("No LZ4 compression service found"); 78 | } 79 | } 80 | 81 | private static void SelectCodec(out ILZ4Service encoder, out ILZ4Service decoder, out ILZ4Service encoderHC) 82 | { 83 | // refer to: http://lz4net.codeplex.com/wikipage?title=Performance%20Testing for explanation about this order 84 | // feel free to change preferred order, just don't do it willy-nilly back it up with some evidence 85 | // it has been tested for Intel on Microsoft .NET only but looks reasonable for Mono as well 86 | if (IntPtr.Size == 4) 87 | { 88 | encoder = 89 | _service_S32 ?? 90 | _service_S64; 91 | decoder = 92 | _service_S64 ?? 93 | _service_S32; 94 | encoderHC = 95 | _service_S32 ?? 96 | _service_S64; 97 | } 98 | else 99 | { 100 | encoder = 101 | _service_S32 ?? 102 | _service_S64; 103 | decoder = 104 | _service_S64 ?? 105 | _service_S32; 106 | encoderHC = 107 | _service_S32 ?? 108 | _service_S64; 109 | } 110 | } 111 | 112 | /// Tries to execute specified action. Ignores exception if it failed. 113 | /// The method. 114 | [MethodImpl(MethodImplOptions.NoInlining)] 115 | private static void Try(Action method) 116 | { 117 | try 118 | { 119 | method(); 120 | } 121 | catch 122 | { 123 | // ignore exception 124 | } 125 | } 126 | 127 | /// Tries to execute specified action. Ignores exception if it failed. 128 | /// Type of result. 129 | /// The method. 130 | /// The default value, returned when action fails. 131 | /// Result of given method, or default value. 132 | [MethodImpl(MethodImplOptions.NoInlining)] 133 | private static T Try(Func method, T defaultValue) 134 | { 135 | try 136 | { 137 | return method(); 138 | } 139 | catch 140 | { 141 | return defaultValue; 142 | } 143 | } 144 | 145 | #endregion 146 | 147 | #region public interface 148 | 149 | /// Gets the name of selected codec(s). 150 | /// The name of the codec. 151 | public static string CodecName 152 | { 153 | get 154 | { 155 | return string.Format( 156 | "{0}/{1}/{2}HC", 157 | Encoder == null ? "" : Encoder.CodecName, 158 | Decoder == null ? "" : Decoder.CodecName, 159 | EncoderHC == null ? "" : EncoderHC.CodecName); 160 | } 161 | } 162 | 163 | /// Get maximum output length. 164 | /// Input length. 165 | /// Output length. 166 | public static int MaximumOutputLength(int inputLength) 167 | { 168 | return inputLength + (inputLength / 255) + 16; 169 | } 170 | 171 | #region Decode 172 | 173 | /// Decodes the specified input. 174 | /// The input. 175 | /// The input offset. 176 | /// Length of the input. 177 | /// The output. 178 | /// The output offset. 179 | /// Length of the output. 180 | /// Set it to true if output length is known. 181 | /// Number of bytes written. 182 | public static int Decode( 183 | byte[] input, 184 | int inputOffset, 185 | int inputLength, 186 | byte[] output, 187 | int outputOffset, 188 | int outputLength = 0, 189 | bool knownOutputLength = false) 190 | { 191 | return Decoder.Decode(input, inputOffset, inputLength, output, outputOffset, outputLength, knownOutputLength); 192 | } 193 | 194 | /// Decodes the specified input. 195 | /// The input. 196 | /// The input offset. 197 | /// Length of the input. 198 | /// Length of the output. 199 | /// Decompressed buffer. 200 | public static byte[] Decode(byte[] input, int inputOffset, int inputLength, int outputLength) 201 | { 202 | if (inputLength < 0) 203 | inputLength = input.Length - inputOffset; 204 | 205 | if (input == null) 206 | throw new ArgumentNullException("input"); 207 | if (inputOffset < 0 || inputOffset + inputLength > input.Length) 208 | throw new ArgumentException("inputOffset and inputLength are invalid for given input"); 209 | 210 | var result = new byte[outputLength]; 211 | var length = Decode(input, inputOffset, inputLength, result, 0, outputLength, true); 212 | if (length != outputLength) 213 | throw new ArgumentException("outputLength is not valid"); 214 | return result; 215 | } 216 | 217 | #endregion 218 | 219 | #endregion 220 | } 221 | } -------------------------------------------------------------------------------- /ZfsSharpLib/lz4net/LZ4/LZ4StreamFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LZ4 4 | { 5 | /// 6 | /// Additional flags for LZ4Stream. 7 | /// 8 | [Flags] 9 | public enum LZ4StreamFlags 10 | { 11 | /// Empty settings. No special behavior. 12 | None = 0x00, 13 | 14 | /// Allows to use interactive mode, possibly returning partial blocks. 15 | InteractiveRead = 0x01, 16 | 17 | /// Uses high compression version of algorithm. 18 | HighCompression = 0x02, 19 | 20 | /// Isolates inner stream so it does not get 21 | /// closed when LZ4 stream is closed. 22 | IsolateInnerStream = 0x04, 23 | 24 | /// Default settings. 25 | Default = None, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ZfsSharpLib/lz4net/LZ4/LZ4StreamMode.cs: -------------------------------------------------------------------------------- 1 | #region license 2 | 3 | /* 4 | Copyright (c) 2013, Milosz Krajewski 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided 8 | that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this list of conditions 11 | and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions 14 | and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 23 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #endregion 27 | 28 | namespace LZ4 29 | { 30 | /// 31 | /// Originally this type comes from System.IO.Compression, 32 | /// but it is not present in portable assemblies. 33 | /// 34 | public enum LZ4StreamMode 35 | { 36 | /// Compress 37 | Compress, 38 | 39 | /// Decompress 40 | Decompress, 41 | } 42 | } -------------------------------------------------------------------------------- /ZfsSharpLib/lz4net/LZ4/Services/Safe32LZ4Service.cs: -------------------------------------------------------------------------------- 1 | #region license 2 | 3 | /* 4 | Copyright (c) 2013, Milosz Krajewski 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided 8 | that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this list of conditions 11 | and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions 14 | and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 23 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #endregion 27 | 28 | namespace LZ4.Services 29 | { 30 | internal class Safe32LZ4Service: ILZ4Service 31 | { 32 | #region ILZ4Service Members 33 | 34 | public string CodecName 35 | { 36 | get { return "Safe 32"; } 37 | } 38 | 39 | public int Encode(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int outputLength) 40 | { 41 | return LZ4ps.LZ4Codec.Encode32(input, inputOffset, inputLength, output, outputOffset, outputLength); 42 | } 43 | 44 | public int Decode(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int outputLength, bool knownOutputLength) 45 | { 46 | return LZ4ps.LZ4Codec.Decode32(input, inputOffset, inputLength, output, outputOffset, outputLength, knownOutputLength); 47 | } 48 | 49 | public int EncodeHC(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int outputLength) 50 | { 51 | return LZ4ps.LZ4Codec.Encode32HC(input, inputOffset, inputLength, output, outputOffset, outputLength); 52 | } 53 | 54 | #endregion 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ZfsSharpLib/lz4net/LZ4/Services/Safe64LZ4Service.cs: -------------------------------------------------------------------------------- 1 | #region license 2 | 3 | /* 4 | Copyright (c) 2013, Milosz Krajewski 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided 8 | that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this list of conditions 11 | and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions 14 | and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 23 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #endregion 27 | 28 | namespace LZ4.Services 29 | { 30 | internal class Safe64LZ4Service: ILZ4Service 31 | { 32 | #region ILZ4Service Members 33 | 34 | public string CodecName 35 | { 36 | get { return "Safe 64"; } 37 | } 38 | 39 | public int Encode(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int outputLength) 40 | { 41 | return LZ4ps.LZ4Codec.Encode64(input, inputOffset, inputLength, output, outputOffset, outputLength); 42 | } 43 | 44 | public int Decode(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int outputLength, bool knownOutputLength) 45 | { 46 | return LZ4ps.LZ4Codec.Decode64(input, inputOffset, inputLength, output, outputOffset, outputLength, knownOutputLength); 47 | } 48 | 49 | public int EncodeHC(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int outputLength) 50 | { 51 | return LZ4ps.LZ4Codec.Encode64HC(input, inputOffset, inputLength, output, outputOffset, outputLength); 52 | } 53 | 54 | #endregion 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ZfsSharpLib/lz4net/LZ4ps/LZ4Codec.cs: -------------------------------------------------------------------------------- 1 | #region license 2 | 3 | /* 4 | Copyright (c) 2013, Milosz Krajewski 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided 8 | that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this list of conditions 11 | and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions 14 | and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 23 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #endregion 27 | 28 | using System; 29 | 30 | /* 31 | NOTE: 32 | This file is shared between LZ4pn and LZ4ps. 33 | If you would like to modify this file please keep in mind that your changes will 34 | affect both projects. 35 | Use 'UNSAFE' conditional define to differentiate 36 | */ 37 | 38 | // ReSharper disable InconsistentNaming 39 | 40 | #if UNSAFE 41 | namespace LZ4pn 42 | #else 43 | namespace LZ4ps 44 | #endif 45 | { 46 | public static partial class LZ4Codec 47 | { 48 | #region configuration 49 | 50 | /// 51 | /// Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) 52 | /// Increasing memory usage improves compression ratio 53 | /// Reduced memory usage can improve speed, due to cache effect 54 | /// Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache 55 | /// 56 | private const int MEMORY_USAGE = 14; 57 | 58 | /// 59 | /// Decreasing this value will make the algorithm skip faster data segments considered "incompressible" 60 | /// This may decrease compression ratio dramatically, but will be faster on incompressible data 61 | /// Increasing this value will make the algorithm search more before declaring a segment "incompressible" 62 | /// This could improve compression a bit, but will be slower on incompressible data 63 | /// The default value (6) is recommended 64 | /// 65 | private const int NOTCOMPRESSIBLE_DETECTIONLEVEL = 6; 66 | 67 | #if !UNSAFE 68 | 69 | /// Buffer length when Buffer.BlockCopy becomes faster than straight loop. 70 | /// Please note that safe implementation REQUIRES it to be greater (not even equal) than 8. 71 | private const int BLOCK_COPY_LIMIT = 16; 72 | 73 | #endif 74 | 75 | #endregion 76 | 77 | #region consts 78 | 79 | private const int MINMATCH = 4; 80 | #pragma warning disable 162 81 | // ReSharper disable once UnreachableCode 82 | private const int SKIPSTRENGTH = 83 | NOTCOMPRESSIBLE_DETECTIONLEVEL > 2 84 | ? NOTCOMPRESSIBLE_DETECTIONLEVEL 85 | : 2; 86 | #pragma warning restore 162 87 | private const int COPYLENGTH = 8; 88 | private const int LASTLITERALS = 5; 89 | private const int MFLIMIT = COPYLENGTH + MINMATCH; 90 | private const int MINLENGTH = MFLIMIT + 1; 91 | private const int MAXD_LOG = 16; 92 | private const int MAXD = 1 << MAXD_LOG; 93 | private const int MAXD_MASK = MAXD - 1; 94 | private const int MAX_DISTANCE = (1 << MAXD_LOG) - 1; 95 | private const int ML_BITS = 4; 96 | private const int ML_MASK = (1 << ML_BITS) - 1; 97 | private const int RUN_BITS = 8 - ML_BITS; 98 | private const int RUN_MASK = (1 << RUN_BITS) - 1; 99 | private const int STEPSIZE_64 = 8; 100 | private const int STEPSIZE_32 = 4; 101 | 102 | private const int LZ4_64KLIMIT = (1 << 16) + (MFLIMIT - 1); 103 | 104 | private const int HASH_LOG = MEMORY_USAGE - 2; 105 | private const int HASH_TABLESIZE = 1 << HASH_LOG; 106 | private const int HASH_ADJUST = (MINMATCH * 8) - HASH_LOG; 107 | 108 | private const int HASH64K_LOG = HASH_LOG + 1; 109 | private const int HASH64K_TABLESIZE = 1 << HASH64K_LOG; 110 | private const int HASH64K_ADJUST = (MINMATCH * 8) - HASH64K_LOG; 111 | 112 | private const int HASHHC_LOG = MAXD_LOG - 1; 113 | private const int HASHHC_TABLESIZE = 1 << HASHHC_LOG; 114 | private const int HASHHC_ADJUST = (MINMATCH * 8) - HASHHC_LOG; 115 | //private const int HASHHC_MASK = HASHHC_TABLESIZE - 1; 116 | 117 | private static readonly int[] DECODER_TABLE_32 = { 0, 3, 2, 3, 0, 0, 0, 0 }; 118 | private static readonly int[] DECODER_TABLE_64 = { 0, 0, 0, -1, 0, 1, 2, 3 }; 119 | 120 | private static readonly int[] DEBRUIJN_TABLE_32 = { 121 | 0, 0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 1, 3, 2, 0, 1, 122 | 3, 3, 1, 2, 2, 2, 2, 0, 3, 1, 2, 0, 1, 0, 1, 1 123 | }; 124 | 125 | private static readonly int[] DEBRUIJN_TABLE_64 = { 126 | 0, 0, 0, 0, 0, 1, 1, 2, 0, 3, 1, 3, 1, 4, 2, 7, 127 | 0, 2, 3, 6, 1, 5, 3, 5, 1, 3, 4, 4, 2, 5, 6, 7, 128 | 7, 0, 1, 2, 3, 3, 4, 6, 2, 6, 5, 5, 3, 4, 5, 6, 129 | 7, 1, 2, 4, 6, 4, 4, 5, 7, 2, 6, 5, 7, 6, 7, 7 130 | }; 131 | 132 | private const int MAX_NB_ATTEMPTS = 256; 133 | private const int OPTIMAL_ML = (ML_MASK - 1) + MINMATCH; 134 | 135 | #endregion 136 | 137 | #region public interface (common) 138 | 139 | /// Gets maximum the length of the output. 140 | /// Length of the input. 141 | /// Maximum number of bytes needed for compressed buffer. 142 | public static int MaximumOutputLength(int inputLength) 143 | { 144 | return inputLength + (inputLength / 255) + 16; 145 | } 146 | 147 | #endregion 148 | 149 | #region internal interface (common) 150 | 151 | internal static void CheckArguments( 152 | Span input, int inputOffset, ref int inputLength, 153 | Span output, int outputOffset, ref int outputLength) 154 | { 155 | if (inputLength < 0) inputLength = input.Length - inputOffset; 156 | if (inputLength == 0) 157 | { 158 | outputLength = 0; 159 | return; 160 | } 161 | 162 | if (input == null) throw new ArgumentNullException("input"); 163 | if (inputOffset < 0 || inputOffset + inputLength > input.Length) 164 | throw new ArgumentException("inputOffset and inputLength are invalid for given input"); 165 | 166 | if (outputLength < 0) outputLength = output.Length - outputOffset; 167 | if (output == null) throw new ArgumentNullException("output"); 168 | if (outputOffset < 0 || outputOffset + outputLength > output.Length) 169 | throw new ArgumentException("outputOffset and outputLength are invalid for given output"); 170 | } 171 | 172 | #endregion 173 | } 174 | } 175 | 176 | // ReSharper restore InconsistentNaming --------------------------------------------------------------------------------