diff --git a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj
index 57ef2b4a0..66600fd73 100644
--- a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj
+++ b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj
@@ -115,6 +115,7 @@
+
diff --git a/DiscImageChef.Filesystems/ZFS.cs b/DiscImageChef.Filesystems/ZFS.cs
index 91b845801..cd7bee833 100644
--- a/DiscImageChef.Filesystems/ZFS.cs
+++ b/DiscImageChef.Filesystems/ZFS.cs
@@ -9,7 +9,7 @@
//
// --[ Description ] ----------------------------------------------------------
//
-// Description
+// Identifies the ZFS filesystem and shows information.
//
// --[ License ] --------------------------------------------------------------
//
@@ -29,14 +29,838 @@
// ----------------------------------------------------------------------------
// Copyright © 2011-2016 Natalia Portillo
// ****************************************************************************/
+
using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
namespace DiscImageChef.Filesystems
{
- public class ZFS
+ /*
+ * The ZFS on-disk structure is quite undocumented, so this has been checked using several test images and reading the comments and headers (but not the code)
+ * of ZFS-On-Linux.
+ *
+ * The most basic structure, the vdev label, is as follows:
+ * 8KiB of blank space
+ * 8KiB reserved for boot code, stored as a ZIO block with magic and checksum
+ * 112KiB of nvlist, usually encoded using XDR
+ * 128KiB of copies of the 1KiB uberblock
+ *
+ * Two vdev labels, L0 and L1 are stored at the start of the vdev.
+ * Another two, L2 and L3 are stored at the end.
+ *
+ * The nvlist is nothing more than a double linked list of name/value pairs where name is a string and value is an arbitrary type (and can be an array of it).
+ * On-disk they are stored sequentially (no pointers) and can be encoded in XDR (an old Sun serialization method that stores everything as 4 bytes chunks) or
+ * natively (that is as the host natively stores that values, for example on Intel an extended float would be 10 bytes (80 bit).
+ * It can also be encoded little or big endian.
+ * Because of this variations, ZFS stored a header indicating the used encoding and endianess before the encoded nvlist.
+ */
+ class ZFS : Filesystem
{
+ const ulong ZEC_Magic = 0x0210DA7AB10C7A11;
+ const ulong ZEC_Cigam = 0x117A0CB17ADA1002;
+
+ struct ZIO_Checksum
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ public ulong[] word;
+ }
+
+ ///
+ /// There is an empty ZIO at sector 16 or sector 31, with magic and checksum, to detect it is really ZFS I suppose.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ZIO_Empty
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 472)]
+ public byte[] empty;
+ public ulong magic;
+ public ZIO_Checksum checksum;
+ }
+
+ // These parameters define how the nvlist is stored
+ const byte NVS_LittleEndian = 1;
+ const byte NVS_BigEndian = 0;
+ const byte NVS_Native = 0;
+ const byte NVS_XDR = 1;
+
+ ///
+ /// This structure indicates which encoding method and endianness is used to encode the nvlist
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct NVS_Method
+ {
+ public byte encoding;
+ public byte endian;
+ public byte reserved1;
+ public byte reserved2;
+ }
+
+ ///
+ /// This structure gives information about the encoded nvlist
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct NVS_XDR_Header
+ {
+ public NVS_Method encodingAndEndian;
+ public uint version;
+ public uint flags;
+ }
+
+ enum NVS_DataTypes : uint
+ {
+ DATA_TYPE_UNKNOWN = 0,
+ DATA_TYPE_BOOLEAN,
+ DATA_TYPE_BYTE,
+ DATA_TYPE_INT16,
+ DATA_TYPE_UINT16,
+ DATA_TYPE_INT32,
+ DATA_TYPE_UINT32,
+ DATA_TYPE_INT64,
+ DATA_TYPE_UINT64,
+ DATA_TYPE_STRING,
+ DATA_TYPE_BYTE_ARRAY,
+ DATA_TYPE_INT16_ARRAY,
+ DATA_TYPE_UINT16_ARRAY,
+ DATA_TYPE_INT32_ARRAY,
+ DATA_TYPE_UINT32_ARRAY,
+ DATA_TYPE_INT64_ARRAY,
+ DATA_TYPE_UINT64_ARRAY,
+ DATA_TYPE_STRING_ARRAY,
+ DATA_TYPE_HRTIME,
+ DATA_TYPE_NVLIST,
+ DATA_TYPE_NVLIST_ARRAY,
+ DATA_TYPE_BOOLEAN_VALUE,
+ DATA_TYPE_INT8,
+ DATA_TYPE_UINT8,
+ DATA_TYPE_BOOLEAN_ARRAY,
+ DATA_TYPE_INT8_ARRAY,
+ DATA_TYPE_UINT8_ARRAY,
+ DATA_TYPE_DOUBLE
+ }
+
+ ///
+ /// This represent an encoded nvpair (an item of an nvlist)
+ ///
+ struct NVS_Item
+ {
+ ///
+ /// Size in bytes when encoded in XDR
+ ///
+ public uint encodedSize;
+ ///
+ /// Size in bytes when decoded
+ ///
+ public uint decodedSize;
+ ///
+ /// On disk, it is null-padded for alignment to 4 bytes and prepended by a 4 byte length indicator
+ ///
+ public string name;
+ ///
+ /// Data type
+ ///
+ public NVS_DataTypes dataType;
+ ///
+ /// How many elements are here
+ ///
+ public uint elements;
+ ///
+ /// On disk size is relative to and always aligned to 4 bytes
+ ///
+ public object value;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct DVA
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
+ public ulong[] word;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct SPA_BlockPointer
+ {
+ ///
+ /// Data virtual address
+ ///
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
+ public DVA[] dataVirtualAddress;
+ ///
+ /// Block properties
+ ///
+ public ulong properties;
+ ///
+ /// Reserved for future expansion
+ ///
+ public ulong[] padding;
+ ///
+ /// TXG when block was allocated
+ ///
+ public ulong birthTxg;
+ ///
+ /// Transaction group at birth
+ ///
+ public ulong birth;
+ ///
+ /// Fill count
+ ///
+ public ulong fill;
+ public ZIO_Checksum checksum;
+ }
+
+ const ulong Uberblock_Magic = 0x00BAB10C;
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ZFS_Uberblock
+ {
+ public ulong magic;
+ public ulong spaVersion;
+ public ulong lastTxg;
+ public ulong guidSum;
+ public ulong timestamp;
+ public SPA_BlockPointer mosPtr;
+ public ulong softwareVersion;
+ }
+
+ const uint ZFS_Magic = 0x58465342;
+
public ZFS()
{
+ Name = "ZFS Filesystem Plugin";
+ PluginUUID = new Guid("0750014F-A714-4692-A369-E23F6EC3659C");
+ }
+
+ public ZFS(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd)
+ {
+ Name = "ZFS Filesystem Plugin";
+ PluginUUID = new Guid("0750014F-A714-4692-A369-E23F6EC3659C");
+ }
+
+ public override bool Identify(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd)
+ {
+ if(imagePlugin.GetSectorSize() < 512)
+ return false;
+
+ byte[] sector;
+ ulong magic;
+
+ if(partitionStart + 31 < partitionEnd)
+ {
+ sector = imagePlugin.ReadSector(partitionStart + 31);
+ magic = BitConverter.ToUInt64(sector, 0x1D8);
+ if(magic == ZEC_Magic || magic == ZEC_Cigam)
+ return true;
+ }
+
+ if(partitionStart + 16 < partitionEnd)
+ {
+ sector = imagePlugin.ReadSector(partitionStart + 16);
+ magic = BitConverter.ToUInt64(sector, 0x1D8);
+ if(magic == ZEC_Magic || magic == ZEC_Cigam)
+ return true;
+ }
+
+ return false;
+ }
+
+ public override void GetInformation(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd, out string information)
+ {
+ information = "";
+ if(imagePlugin.GetSectorSize() < 512)
+ return;
+
+ byte[] sector;
+ ulong magic;
+
+ ulong nvlistOff = 32;
+ uint nvlistLen = 114688 / imagePlugin.ImageInfo.sectorSize;
+ byte[] nvlist;
+
+ if(partitionStart + 31 < partitionEnd)
+ {
+ sector = imagePlugin.ReadSector(partitionStart + 31);
+ magic = BitConverter.ToUInt64(sector, 0x1D8);
+ if(magic == ZEC_Magic || magic == ZEC_Cigam)
+ nvlistOff = 32;
+ }
+
+ if(partitionStart + 16 < partitionEnd)
+ {
+ sector = imagePlugin.ReadSector(partitionStart + 16);
+ magic = BitConverter.ToUInt64(sector, 0x1D8);
+ if(magic == ZEC_Magic || magic == ZEC_Cigam)
+ nvlistOff = 17;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine("ZFS filesystem");
+
+ nvlist = imagePlugin.ReadSectors(partitionStart + nvlistOff, nvlistLen);
+ Dictionary decodedNvList;
+
+ if(!DecodeNvList(nvlist, out decodedNvList))
+ sb.AppendLine("Could not decode nvlist");
+ else
+ sb.AppendLine(PrintNvList(decodedNvList));
+
+ information = sb.ToString();
+
+ NVS_Item tmpObj;
+
+ xmlFSType = new Schemas.FileSystemType();
+ xmlFSType.Type = "ZFS filesystem";
+ if(decodedNvList.TryGetValue("name", out tmpObj))
+ xmlFSType.VolumeName = (string)tmpObj.value;
+ if(decodedNvList.TryGetValue("guid", out tmpObj))
+ xmlFSType.VolumeSerial = string.Format("{0}", (ulong)tmpObj.value);
+ if(decodedNvList.TryGetValue("pool_guid", out tmpObj))
+ xmlFSType.VolumeSetIdentifier = string.Format("{0}", (ulong)tmpObj.value);
+ }
+
+ static bool DecodeNvList(byte[] nvlist, out Dictionary decodedNvList)
+ {
+ byte[] tmp = new byte[nvlist.Length - 4];
+ Array.Copy(nvlist, 4, tmp, 0, nvlist.Length - 4);
+ bool xdr = nvlist[0] == 1;
+ bool littleEndian = nvlist[1] == 1;
+
+ return DecodeNvList(tmp, out decodedNvList, xdr, littleEndian);
+ }
+
+ // TODO: Decode native nvlist
+ static bool DecodeNvList(byte[] nvlist, out Dictionary decodedNvList, bool xdr, bool littleEndian)
+ {
+ decodedNvList = new Dictionary();
+
+ if(nvlist == null || nvlist.Length < 16)
+ return false;
+
+ int offset = 0;
+ if(!xdr)
+ return false;
+
+ BigEndianBitConverter.IsLittleEndian = littleEndian;
+
+ offset = 8;
+ while(offset < nvlist.Length)
+ {
+ uint nameLength;
+ byte[] nameBytes;
+ NVS_Item item = new NVS_Item();
+ int currOff = offset;
+
+ item.encodedSize = BigEndianBitConverter.ToUInt32(nvlist, offset);
+
+ // Finished
+ if(item.encodedSize == 0)
+ break;
+
+ offset += 4;
+ item.decodedSize = BigEndianBitConverter.ToUInt32(nvlist, offset);
+ offset += 4;
+ nameLength = BigEndianBitConverter.ToUInt32(nvlist, offset);
+ offset += 4;
+ if(nameLength % 4 > 0)
+ nameLength += 4 - (nameLength % 4);
+ nameBytes = new byte[nameLength];
+ Array.Copy(nvlist, offset, nameBytes, 0, nameLength);
+ item.name = StringHandlers.CToString(nameBytes);
+ offset += (int)nameLength;
+ item.dataType = (NVS_DataTypes)BigEndianBitConverter.ToUInt32(nvlist, offset);
+ offset += 4;
+ item.elements = BigEndianBitConverter.ToUInt32(nvlist, offset);
+ offset += 4;
+
+ if(item.elements == 0)
+ {
+ decodedNvList.Add(item.name, item);
+ continue;
+ }
+
+ switch(item.dataType)
+ {
+ case NVS_DataTypes.DATA_TYPE_BOOLEAN:
+ case NVS_DataTypes.DATA_TYPE_BOOLEAN_ARRAY:
+ case NVS_DataTypes.DATA_TYPE_BOOLEAN_VALUE:
+ if(item.elements > 1)
+ {
+ bool[] boolArray = new bool[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ uint temp = BigEndianBitConverter.ToUInt32(nvlist, offset);
+ boolArray[i] = temp > 0;
+ offset += 4;
+ }
+ item.value = boolArray;
+ }
+ else
+ {
+ uint temp = BigEndianBitConverter.ToUInt32(nvlist, offset);
+ item.value = (temp > 0);
+ offset += 4;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_BYTE:
+ case NVS_DataTypes.DATA_TYPE_BYTE_ARRAY:
+ case NVS_DataTypes.DATA_TYPE_UINT8:
+ case NVS_DataTypes.DATA_TYPE_UINT8_ARRAY:
+ if(item.elements > 1)
+ {
+ byte[] byteArray = new byte[item.elements];
+ Array.Copy(nvlist, offset, byteArray, 0, item.elements);
+ offset += (int)item.elements;
+ if(item.elements % 4 > 0)
+ offset += 4 - (int)(item.elements % 4);
+ item.value = byteArray;
+ }
+ else
+ {
+ item.value = nvlist[offset];
+ offset += 4;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_DOUBLE:
+ if(item.elements > 1)
+ {
+ double[] doubleArray = new double[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ double temp = BigEndianBitConverter.ToDouble(nvlist, offset);
+ doubleArray[i] = temp;
+ offset += 8;
+ }
+ item.value = doubleArray;
+ }
+ else
+ {
+ item.value = BigEndianBitConverter.ToDouble(nvlist, offset);
+ offset += 8;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_HRTIME:
+ if(item.elements > 1)
+ {
+ DateTime[] hrtimeArray = new DateTime[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ DateTime temp = DateHandlers.UNIXHrTimeToDateTime(BigEndianBitConverter.ToUInt64(nvlist, offset));
+ hrtimeArray[i] = temp;
+ offset += 8;
+ }
+ item.value = hrtimeArray;
+ }
+ else
+ {
+ item.value = DateHandlers.UNIXHrTimeToDateTime(BigEndianBitConverter.ToUInt64(nvlist, offset));
+ offset += 8;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_INT16:
+ case NVS_DataTypes.DATA_TYPE_INT16_ARRAY:
+ if(item.elements > 1)
+ {
+ short[] shortArray = new short[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ short temp = BigEndianBitConverter.ToInt16(nvlist, offset);
+ shortArray[i] = temp;
+ offset += 4;
+ }
+ item.value = shortArray;
+ }
+ else
+ {
+ item.value = BigEndianBitConverter.ToInt16(nvlist, offset);
+ offset += 4;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_INT32:
+ case NVS_DataTypes.DATA_TYPE_INT32_ARRAY:
+ if(item.elements > 1)
+ {
+ int[] intArray = new int[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ int temp = BigEndianBitConverter.ToInt32(nvlist, offset);
+ intArray[i] = temp;
+ offset += 4;
+ }
+ item.value = intArray;
+ }
+ else
+ {
+ item.value = BigEndianBitConverter.ToInt32(nvlist, offset);
+ offset += 4;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_INT64:
+ case NVS_DataTypes.DATA_TYPE_INT64_ARRAY:
+ if(item.elements > 1)
+ {
+ long[] longArray = new long[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ long temp = BigEndianBitConverter.ToInt64(nvlist, offset);
+ longArray[i] = temp;
+ offset += 8;
+ }
+ item.value = longArray;
+ }
+ else
+ {
+ item.value = BigEndianBitConverter.ToInt64(nvlist, offset);
+ offset += 8;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_INT8:
+ case NVS_DataTypes.DATA_TYPE_INT8_ARRAY:
+ if(item.elements > 1)
+ {
+ sbyte[] sbyteArray = new sbyte[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ sbyte temp = (sbyte)nvlist[offset];
+ sbyteArray[i] = temp;
+ offset++;
+ }
+ item.value = sbyteArray;
+ if(sbyteArray.Length % 4 > 0)
+ offset += 4 - sbyteArray.Length % 4;
+ }
+ else
+ {
+ item.value = BigEndianBitConverter.ToInt64(nvlist, offset);
+ offset += 4;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_STRING:
+ case NVS_DataTypes.DATA_TYPE_STRING_ARRAY:
+ if(item.elements > 1)
+ {
+ string[] stringArray = new string[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ uint strLength = BigEndianBitConverter.ToUInt32(nvlist, offset);
+ offset += 4;
+ byte[] strBytes = new byte[strLength];
+ Array.Copy(nvlist, offset, strBytes, 0, strLength);
+ stringArray[i] = StringHandlers.CToString(strBytes);
+ offset += (int)strLength;
+ if(strLength % 4 > 0)
+ offset += 4 - (int)(strLength % 4);
+ }
+ item.value = stringArray;
+ }
+ else
+ {
+ uint strLength = BigEndianBitConverter.ToUInt32(nvlist, offset);
+ offset += 4;
+ byte[] strBytes = new byte[strLength];
+ Array.Copy(nvlist, offset, strBytes, 0, strLength);
+ item.value = StringHandlers.CToString(strBytes);
+ offset += (int)strLength;
+ if(strLength % 4 > 0)
+ offset += 4 - (int)(strLength % 4);
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_UINT16:
+ case NVS_DataTypes.DATA_TYPE_UINT16_ARRAY:
+ if(item.elements > 1)
+ {
+ ushort[] ushortArray = new ushort[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ ushort temp = BigEndianBitConverter.ToUInt16(nvlist, offset);
+ ushortArray[i] = temp;
+ offset += 4;
+ }
+ item.value = ushortArray;
+ }
+ else
+ {
+ item.value = BigEndianBitConverter.ToUInt16(nvlist, offset);
+ offset += 4;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_UINT32:
+ case NVS_DataTypes.DATA_TYPE_UINT32_ARRAY:
+ if(item.elements > 1)
+ {
+ uint[] uintArray = new uint[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ uint temp = BigEndianBitConverter.ToUInt32(nvlist, offset);
+ uintArray[i] = temp;
+ offset += 4;
+ }
+ item.value = uintArray;
+ }
+ else
+ {
+ item.value = BigEndianBitConverter.ToUInt32(nvlist, offset);
+ offset += 4;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_UINT64:
+ case NVS_DataTypes.DATA_TYPE_UINT64_ARRAY:
+ if(item.elements > 1)
+ {
+ ulong[] ulongArray = new ulong[item.elements];
+ for(int i = 0; i < item.elements; i++)
+ {
+ ulong temp = BigEndianBitConverter.ToUInt64(nvlist, offset);
+ ulongArray[i] = temp;
+ offset += 8;
+ }
+ item.value = ulongArray;
+ }
+ else
+ {
+ item.value = BigEndianBitConverter.ToUInt64(nvlist, offset);
+ offset += 8;
+ }
+ break;
+ case NVS_DataTypes.DATA_TYPE_NVLIST:
+ if(item.elements > 1)
+ goto default;
+
+ byte[] subListBytes = new byte[item.encodedSize - (offset - currOff)];
+ Array.Copy(nvlist, offset, subListBytes, 0, subListBytes.Length);
+ Dictionary subList;
+ if(DecodeNvList(subListBytes, out subList, xdr, littleEndian))
+ item.value = subList;
+ else
+ goto default;
+ offset = (int)(currOff + item.encodedSize);
+ break;
+ default:
+ byte[] unknown = new byte[item.encodedSize - (offset - currOff)];
+ Array.Copy(nvlist, offset, unknown, 0, unknown.Length);
+ item.value = unknown;
+ offset = (int)(currOff + item.encodedSize);
+ break;
+ }
+
+ decodedNvList.Add(item.name, item);
+ }
+
+ return decodedNvList.Count > 0;
+ }
+
+ static string PrintNvList(Dictionary decodedNvList)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach(NVS_Item item in decodedNvList.Values)
+ {
+ if(item.elements == 0)
+ {
+ sb.AppendFormat("{0} is not set", item.name).AppendLine();
+ continue;
+ }
+
+ switch(item.dataType)
+ {
+ case NVS_DataTypes.DATA_TYPE_BOOLEAN:
+ case NVS_DataTypes.DATA_TYPE_BOOLEAN_ARRAY:
+ case NVS_DataTypes.DATA_TYPE_BOOLEAN_VALUE:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((bool[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (bool)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_BYTE:
+ case NVS_DataTypes.DATA_TYPE_BYTE_ARRAY:
+ case NVS_DataTypes.DATA_TYPE_UINT8:
+ case NVS_DataTypes.DATA_TYPE_UINT8_ARRAY:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((byte[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (byte)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_DOUBLE:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((double[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (double)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_HRTIME:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((DateTime[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (DateTime)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_INT16:
+ case NVS_DataTypes.DATA_TYPE_INT16_ARRAY:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((short[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (short)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_INT32:
+ case NVS_DataTypes.DATA_TYPE_INT32_ARRAY:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((int[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (int)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_INT64:
+ case NVS_DataTypes.DATA_TYPE_INT64_ARRAY:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((long[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (long)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_INT8:
+ case NVS_DataTypes.DATA_TYPE_INT8_ARRAY:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((sbyte[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (sbyte)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_STRING:
+ case NVS_DataTypes.DATA_TYPE_STRING_ARRAY:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((string[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (string)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_UINT16:
+ case NVS_DataTypes.DATA_TYPE_UINT16_ARRAY:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((ushort[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (ushort)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_UINT32:
+ case NVS_DataTypes.DATA_TYPE_UINT32_ARRAY:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((uint[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (uint)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_UINT64:
+ case NVS_DataTypes.DATA_TYPE_UINT64_ARRAY:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = {2}", item.name, i, ((ulong[])item.value)[i]).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = {1}", item.name, (ulong)item.value).AppendLine();
+ break;
+ case NVS_DataTypes.DATA_TYPE_NVLIST:
+ if(item.elements == 1)
+ sb.AppendFormat("{0} =\n{1}", item.name, PrintNvList((Dictionary)item.value)).AppendLine();
+ else
+ sb.AppendFormat("{0} = {1} elements nvlist[], unable to print", item.name, item.elements).AppendLine();
+ break;
+ default:
+ if(item.elements > 1)
+ {
+ for(int i = 0; i < item.elements; i++)
+ sb.AppendFormat("{0}[{1}] = Unknown data type {2}", item.name, i, item.dataType).AppendLine();
+ }
+ else
+ sb.AppendFormat("{0} = Unknown data type {1}", item.name, item.dataType).AppendLine();
+ break;
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public override Errno Mount()
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno Mount(bool debug)
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno Unmount()
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno MapBlock(string path, long fileBlock, ref long deviceBlock)
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno GetAttributes(string path, ref FileAttributes attributes)
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno ListXAttr(string path, ref List xattrs)
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno GetXattr(string path, string xattr, ref byte[] buf)
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno Read(string path, long offset, long size, ref byte[] buf)
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno ReadDir(string path, ref List contents)
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno StatFs(ref FileSystemInfo stat)
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno Stat(string path, ref FileEntryInfo stat)
+ {
+ return Errno.NotImplemented;
+ }
+
+ public override Errno ReadLink(string path, ref string dest)
+ {
+ return Errno.NotImplemented;
}
}
-}
-
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index c35527be8..832cb16eb 100644
--- a/README.md
+++ b/README.md
@@ -142,6 +142,7 @@ Supported file systems for identification and information only
* Universal Disk Format (UDF)
* ECMA-67: 130mm Flexible Disk Cartridge Labelling and File Structure for Information Interchange
* Xbox filesystems
+* Zettabyte File System (ZFS)
Supported checksums
===================
diff --git a/TODO b/TODO
index 4ba122dfb..bd1d0f719 100644
--- a/TODO
+++ b/TODO
@@ -10,7 +10,6 @@
Filesystem plugins:
--- Add support for Apple DOS filesystems
---- Add support for ZFS
--- Add support for NwFS
--- Add support for ReFS