/**
 * @file Program.cs
 *
 * @copyright 2015-2026 Bill Zissimopoulos
 */
/*
 * This file is part of WinFsp.
 *
 * You can redistribute it and/or modify it under the terms of the GNU
 * General Public License version 3 as published by the Free Software
 * Foundation.
 *
 * Licensees holding a valid commercial license may use this software
 * in accordance with the commercial license agreement provided in
 * conjunction with the software.  The terms and conditions of any such
 * commercial license agreement shall govern, supersede, and render
 * ineffective any application of the GPLv3 license to this software,
 * notwithstanding of any reference thereto in the software or
 * associated repository.
 */

#define MEMFS_SLOWIO

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Threading;
#if MEMFS_SLOWIO
using System.Threading.Tasks;
#endif

using Fsp;
using VolumeInfo = Fsp.Interop.VolumeInfo;
using FileInfo = Fsp.Interop.FileInfo;

namespace memfs
{
    class Path
    {
        public static String GetDirectoryName(String Path)
        {
            int Index = Path.LastIndexOf('\\');
            if (0 > Index)
                return Path;
            else if (0 == Index)
                return "\\";
            else
                return Path.Substring(0, Index);
        }

        public static String GetFileName(String Path)
        {
            int Index = Path.LastIndexOf('\\');
            if (0 > Index)
                return Path;
            else
                return Path.Substring(Index + 1);
        }
    }

    struct EaValueData
    {
        public Byte[] EaValue;
        public Boolean NeedEa;
    }

    class FileNode
    {
        public FileNode(String FileName)
        {
            this.FileName = FileName;
            FileInfo.CreationTime =
            FileInfo.LastAccessTime =
            FileInfo.LastWriteTime =
            FileInfo.ChangeTime = (UInt64)DateTime.Now.ToFileTimeUtc();
            FileInfo.IndexNumber = IndexNumber++;
        }
        public FileInfo GetFileInfo()
        {
            if (null == MainFileNode)
                return this.FileInfo;
            else
            {
                FileInfo FileInfo = MainFileNode.FileInfo;
                FileInfo.FileAttributes &= ~(UInt32)FileAttributes.Directory;
                    /* named streams cannot be directories */
                FileInfo.AllocationSize = this.FileInfo.AllocationSize;
                FileInfo.FileSize = this.FileInfo.FileSize;
                return FileInfo;
            }
        }
        public SortedDictionary<String, EaValueData> GetEaMap(Boolean Force)
        {
            FileNode FileNode = null == MainFileNode ? this : MainFileNode;
            if (null == EaMap && Force)
                EaMap = new SortedDictionary<String, EaValueData>(StringComparer.OrdinalIgnoreCase);
            return EaMap;
        }

        private static UInt64 IndexNumber = 1;
        public String FileName;
        public FileInfo FileInfo;
        public Byte[] FileSecurity;
        public Byte[] FileData;
        public Byte[] ReparseData;
        private SortedDictionary<String, EaValueData> EaMap;
        public FileNode MainFileNode;
        public int OpenCount;
    }

    class FileNodeMap
    {
        public FileNodeMap(Boolean CaseInsensitive)
        {
            this.CaseInsensitive = CaseInsensitive;
            StringComparer Comparer = CaseInsensitive ?
                StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
            Set = new SortedSet<String>(Comparer);
            Map = new Dictionary<String, FileNode>(Comparer);
        }
        public UInt32 Count()
        {
            return (UInt32)Map.Count;
        }
        public FileNode Get(String FileName)
        {
            FileNode FileNode;
            return Map.TryGetValue(FileName, out FileNode) ? FileNode : null;
        }
        public FileNode GetMain(String FileName)
        {
            int Index = FileName.IndexOf(':');
            if (0 > Index)
                return null;
            FileNode FileNode;
            return Map.TryGetValue(FileName.Substring(0, Index), out FileNode) ? FileNode : null;
        }
        public FileNode GetParent(String FileName, ref Int32 Result)
        {
            FileNode FileNode;
            Map.TryGetValue(Path.GetDirectoryName(FileName), out FileNode);
            if (null == FileNode)
            {
                Result = FileSystemBase.STATUS_OBJECT_PATH_NOT_FOUND;
                return null;
            }
            if (0 == (FileNode.FileInfo.FileAttributes & (UInt32)FileAttributes.Directory))
            {
                Result = FileSystemBase.STATUS_NOT_A_DIRECTORY;
                return null;
            }
            return FileNode;
        }
        public void TouchParent(FileNode FileNode)
        {
            if ("\\" == FileNode.FileName)
                return;
            Int32 Result = FileSystemBase.STATUS_SUCCESS;
            FileNode Parent = GetParent(FileNode.FileName, ref Result);
            if (null == Parent)
                return;
            Parent.FileInfo.LastAccessTime =
            Parent.FileInfo.LastWriteTime =
            Parent.FileInfo.ChangeTime = (UInt64)DateTime.Now.ToFileTimeUtc();
        }
        public void Insert(FileNode FileNode)
        {
            Set.Add(FileNode.FileName);
            Map.Add(FileNode.FileName, FileNode);
            TouchParent(FileNode);
        }
        public void Remove(FileNode FileNode)
        {
            if (Set.Remove(FileNode.FileName))
            {
                Map.Remove(FileNode.FileName);
                TouchParent(FileNode);
            }
        }
        public Boolean HasChild(FileNode FileNode)
        {
            foreach (String Name in GetChildrenFileNames(FileNode, null))
                return true;
            return false;
        }
        public IEnumerable<String> GetChildrenFileNames(FileNode FileNode, String Marker)
        {
            String MinName = "\\";
            String MaxName = "]";
            if ("\\" != FileNode.FileName)
            {
                MinName = FileNode.FileName + "\\";
                MaxName = FileNode.FileName + "]";
            }
            if (null != Marker)
                MinName += Marker;
            foreach (String Name in Set.GetViewBetween(MinName, MaxName))
                if (Name != MinName &&
                    Name.Length > MaxName.Length && -1 == Name.IndexOfAny(Delimiters, MaxName.Length))
                    yield return Name;
        }
        public IEnumerable<String> GetStreamFileNames(FileNode FileNode)
        {
            String MinName = FileNode.FileName + ":";
            String MaxName = FileNode.FileName + ";";
            foreach (String Name in Set.GetViewBetween(MinName, MaxName))
                if (Name.Length > MinName.Length)
                    yield return Name;
        }
        public IEnumerable<String> GetDescendantFileNames(FileNode FileNode)
        {
            yield return FileNode.FileName;
            String MinName = FileNode.FileName + ":";
            String MaxName = FileNode.FileName + ";";
            foreach (String Name in Set.GetViewBetween(MinName, MaxName))
                if (Name.Length > MinName.Length)
                    yield return Name;
            MinName = "\\";
            MaxName = "]";
            if ("\\" != FileNode.FileName)
            {
                MinName = FileNode.FileName + "\\";
                MaxName = FileNode.FileName + "]";
            }
            foreach (String Name in Set.GetViewBetween(MinName, MaxName))
                if (Name.Length > MinName.Length)
                    yield return Name;
        }

        private static readonly Char[] Delimiters = new Char[] { '\\', ':' };
        public Boolean CaseInsensitive;
        private SortedSet<String> Set;
        private Dictionary<String, FileNode> Map;
    }

    class Memfs : FileSystemBase
    {
        private FileSystemHost Host;
        public const UInt16 MEMFS_SECTOR_SIZE = 512;
        public const UInt16 MEMFS_SECTORS_PER_ALLOCATION_UNIT = 1;

        public Memfs(
            Boolean CaseInsensitive, UInt32 MaxFileNodes, UInt32 MaxFileSize, String RootSddl,
            UInt64 SlowioMaxDelay, UInt64 SlowioPercentDelay, UInt64 SlowioRarefyDelay)
        {
            this.FileNodeMap = new FileNodeMap(CaseInsensitive);
            this.MaxFileNodes = MaxFileNodes;
            this.MaxFileSize = MaxFileSize;
            this.SlowioMaxDelay = SlowioMaxDelay;
            this.SlowioPercentDelay = SlowioPercentDelay;
            this.SlowioRarefyDelay = SlowioRarefyDelay;

            /*
             * Create root directory.
             */

            FileNode RootNode = new FileNode("\\");
            RootNode.FileInfo.FileAttributes = (UInt32)FileAttributes.Directory;
            if (null == RootSddl)
                RootSddl = "O:BAG:BAD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;WD)";
            RawSecurityDescriptor RootSecurityDescriptor = new RawSecurityDescriptor(RootSddl);
            RootNode.FileSecurity = new Byte[RootSecurityDescriptor.BinaryLength];
            RootSecurityDescriptor.GetBinaryForm(RootNode.FileSecurity, 0);

            FileNodeMap.Insert(RootNode);
        }

        public override Int32 Init(Object Host0)
        {
            Host = (FileSystemHost)Host0;
            Host.SectorSize = Memfs.MEMFS_SECTOR_SIZE;
            Host.SectorsPerAllocationUnit = Memfs.MEMFS_SECTORS_PER_ALLOCATION_UNIT;
            Host.VolumeCreationTime = (UInt64)DateTime.Now.ToFileTimeUtc();
            Host.VolumeSerialNumber = (UInt32)(Host.VolumeCreationTime / (10000 * 1000));
            Host.CaseSensitiveSearch = !FileNodeMap.CaseInsensitive;
            Host.CasePreservedNames = true;
            Host.UnicodeOnDisk = true;
            Host.PersistentAcls = true;
            Host.ReparsePoints = true;
            Host.ReparsePointsAccessCheck = false;
            Host.NamedStreams = true;
            Host.PostCleanupWhenModifiedOnly = true;
            Host.PostDispositionWhenNecessaryOnly = true;
            Host.PassQueryDirectoryFileName = true;
            Host.ExtendedAttributes = true;
            Host.WslFeatures = true;
            Host.RejectIrpPriorToTransact0 = true;
            Host.SupportsPosixUnlinkRename = true;
            return STATUS_SUCCESS;
        }

#if MEMFS_SLOWIO
        public override int Mounted(object Host)
        {
            SlowioTasksRunning = 0;
            return STATUS_SUCCESS;
        }

        public override void Unmounted(object Host)
        {
            while (SlowioTasksRunning != 0)
                Thread.Sleep(1000);
        }
#endif

        public override Int32 GetVolumeInfo(
            out VolumeInfo VolumeInfo)
        {
            VolumeInfo = default(VolumeInfo);
            VolumeInfo.TotalSize = MaxFileNodes * (UInt64)MaxFileSize;
            VolumeInfo.FreeSize = (MaxFileNodes - FileNodeMap.Count()) * (UInt64)MaxFileSize;
            VolumeInfo.SetVolumeLabel(VolumeLabel);
            return STATUS_SUCCESS;
        }

        public override Int32 SetVolumeLabel(
            String VolumeLabel,
            out VolumeInfo VolumeInfo)
        {
            this.VolumeLabel = VolumeLabel;
            return GetVolumeInfo(out VolumeInfo);
        }

        public override Int32 GetSecurityByName(
            String FileName,
            out UInt32 FileAttributes/* or ReparsePointIndex */,
            ref Byte[] SecurityDescriptor)
        {
            FileNode FileNode = FileNodeMap.Get(FileName);
            if (null == FileNode)
            {
                Int32 Result = STATUS_OBJECT_NAME_NOT_FOUND;
                if (FindReparsePoint(FileName, out FileAttributes))
                    Result = STATUS_REPARSE;
                else
                    FileNodeMap.GetParent(FileName, ref Result);
                return Result;
            }

            UInt32 FileAttributesMask = ~(UInt32)0;
            if (null != FileNode.MainFileNode)
            {
                FileAttributesMask = ~(UInt32)System.IO.FileAttributes.Directory;
                FileNode = FileNode.MainFileNode;
            }
            FileAttributes = FileNode.FileInfo.FileAttributes & FileAttributesMask;
            if (null != SecurityDescriptor)
                SecurityDescriptor = FileNode.FileSecurity;

            return STATUS_SUCCESS;
        }

        public override Int32 CreateEx(
            String FileName,
            UInt32 CreateOptions,
            UInt32 GrantedAccess,
            UInt32 FileAttributes,
            Byte[] SecurityDescriptor,
            UInt64 AllocationSize,
            IntPtr ExtraBuffer,
            UInt32 ExtraLength,
            Boolean ExtraBufferIsReparsePoint,
            out Object FileNode0,
            out Object FileDesc,
            out FileInfo FileInfo,
            out String NormalizedName)
        {
            FileNode0 = default(Object);
            FileDesc = default(Object);
            FileInfo = default(FileInfo);
            NormalizedName = default(String);

            FileNode FileNode;
            FileNode ParentNode;
            Int32 Result = STATUS_SUCCESS;

            FileNode = FileNodeMap.Get(FileName);
            if (null != FileNode)
                return STATUS_OBJECT_NAME_COLLISION;
            ParentNode = FileNodeMap.GetParent(FileName, ref Result);
            if (null == ParentNode)
                return Result;

            if (0 != (CreateOptions & FILE_DIRECTORY_FILE))
                AllocationSize = 0;
            if (FileNodeMap.Count() >= MaxFileNodes)
                return STATUS_CANNOT_MAKE;
            if (AllocationSize > MaxFileSize)
                return STATUS_DISK_FULL;

            if ("\\" != ParentNode.FileName)
                /* normalize name */
                FileName = ParentNode.FileName + "\\" + Path.GetFileName(FileName);
            FileNode = new FileNode(FileName);
            FileNode.MainFileNode = FileNodeMap.GetMain(FileName);
            FileNode.FileInfo.FileAttributes = 0 != (FileAttributes & (UInt32)System.IO.FileAttributes.Directory) ?
                FileAttributes : FileAttributes | (UInt32)System.IO.FileAttributes.Archive;
            FileNode.FileSecurity = SecurityDescriptor;
            if (IntPtr.Zero != ExtraBuffer)
            {
                if (!ExtraBufferIsReparsePoint)
                {
                    Result = SetEaEntries(FileNode, null, ExtraBuffer, ExtraLength);
                    if (0 > Result)
                        return Result;
                }
                else
                {
                    Byte[] ReparseData = MakeReparsePoint(ExtraBuffer, ExtraLength);
                    FileNode.FileInfo.FileAttributes |= (UInt32)System.IO.FileAttributes.ReparsePoint;
                    FileNode.FileInfo.ReparseTag = GetReparseTag(ReparseData);
                    FileNode.ReparseData = ReparseData;
                }
            }
            if (0 != AllocationSize)
            {
                Result = SetFileSizeInternal(FileNode, AllocationSize, true);
                if (0 > Result)
                    return Result;
            }
            FileNodeMap.Insert(FileNode);

            Interlocked.Increment(ref FileNode.OpenCount);
            FileNode0 = FileNode;
            FileInfo = FileNode.GetFileInfo();
            NormalizedName = FileNode.FileName;

            return STATUS_SUCCESS;
        }

        public override Int32 Open(
            String FileName,
            UInt32 CreateOptions,
            UInt32 GrantedAccess,
            out Object FileNode0,
            out Object FileDesc,
            out FileInfo FileInfo,
            out String NormalizedName)
        {
            FileNode0 = default(Object);
            FileDesc = default(Object);
            FileInfo = default(FileInfo);
            NormalizedName = default(String);

            FileNode FileNode;
            Int32 Result;

            FileNode = FileNodeMap.Get(FileName);
            if (null == FileNode)
            {
                Result = STATUS_OBJECT_NAME_NOT_FOUND;
                FileNodeMap.GetParent(FileName, ref Result);
                return Result;
            }

            if (0 != (CreateOptions & FILE_NO_EA_KNOWLEDGE) &&
                null == FileNode.MainFileNode)
            {
                SortedDictionary<String, EaValueData> EaMap = FileNode.GetEaMap(false);
                if (null != EaMap)
                {
                    foreach (KeyValuePair<String, EaValueData> Pair in EaMap)
                        if (Pair.Value.NeedEa)
                            return STATUS_ACCESS_DENIED;
                }
            }

            Interlocked.Increment(ref FileNode.OpenCount);
            FileNode0 = FileNode;
            FileInfo = FileNode.GetFileInfo();
            NormalizedName = FileNode.FileName;

            return STATUS_SUCCESS;
        }

        public override Int32 OverwriteEx(
            Object FileNode0,
            Object FileDesc,
            UInt32 FileAttributes,
            Boolean ReplaceFileAttributes,
            UInt64 AllocationSize,
            IntPtr Ea,
            UInt32 EaLength,
            out FileInfo FileInfo)
        {
            FileInfo = default(FileInfo);

            FileNode FileNode = (FileNode)FileNode0;
            Int32 Result;

            List<String> StreamFileNames = new List<String>(FileNodeMap.GetStreamFileNames(FileNode));
            foreach (String StreamFileName in StreamFileNames)
            {
                FileNode StreamNode = FileNodeMap.Get(StreamFileName);
                if (null == StreamNode)
                    continue; /* should not happen */
                if (0 == StreamNode.OpenCount)
                    FileNodeMap.Remove(StreamNode);
            }

            SortedDictionary<String, EaValueData> EaMap = FileNode.GetEaMap(false);
            if (null != EaMap)
            {
                EaMap.Clear();
                FileNode.FileInfo.EaSize = 0;
            }
            if (IntPtr.Zero != Ea)
            {
                Result = SetEaEntries(FileNode, null, Ea, EaLength);
                if (0 > Result)
                    return Result;
            }

            Result = SetFileSizeInternal(FileNode, AllocationSize, true);
            if (0 > Result)
                return Result;
            if (ReplaceFileAttributes)
                FileNode.FileInfo.FileAttributes = FileAttributes | (UInt32)System.IO.FileAttributes.Archive;
            else
                FileNode.FileInfo.FileAttributes |= FileAttributes | (UInt32)System.IO.FileAttributes.Archive;
            FileNode.FileInfo.FileSize = 0;
            FileNode.FileInfo.LastAccessTime =
            FileNode.FileInfo.LastWriteTime =
            FileNode.FileInfo.ChangeTime = (UInt64)DateTime.Now.ToFileTimeUtc();

            FileInfo = FileNode.GetFileInfo();

            return STATUS_SUCCESS;
        }

        public override void Cleanup(
            Object FileNode0,
            Object FileDesc,
            String FileName,
            UInt32 Flags)
        {
            FileNode FileNode = (FileNode)FileNode0;
            FileNode MainFileNode = null != FileNode.MainFileNode ?
                FileNode.MainFileNode : FileNode;

            if (0 != (Flags & CleanupSetArchiveBit))
            {
                if (0 == (MainFileNode.FileInfo.FileAttributes & (UInt32)FileAttributes.Directory))
                    MainFileNode.FileInfo.FileAttributes |= (UInt32)FileAttributes.Archive;
            }

            if (0 != (Flags & (CleanupSetLastAccessTime | CleanupSetLastWriteTime | CleanupSetChangeTime)))
            {
                UInt64 SystemTime = (UInt64)DateTime.Now.ToFileTimeUtc();

                if (0 != (Flags & CleanupSetLastAccessTime))
                    MainFileNode.FileInfo.LastAccessTime = SystemTime;
                if (0 != (Flags & CleanupSetLastWriteTime))
                    MainFileNode.FileInfo.LastWriteTime = SystemTime;
                if (0 != (Flags & CleanupSetChangeTime))
                    MainFileNode.FileInfo.ChangeTime = SystemTime;
            }

            if (0 != (Flags & CleanupSetAllocationSize))
            {
                UInt64 AllocationUnit = MEMFS_SECTOR_SIZE * MEMFS_SECTORS_PER_ALLOCATION_UNIT;
                UInt64 AllocationSize = (FileNode.FileInfo.FileSize + AllocationUnit - 1) /
                    AllocationUnit * AllocationUnit;
                SetFileSizeInternal(FileNode, AllocationSize, true);
            }

            if (0 != (Flags & CleanupDelete) && !FileNodeMap.HasChild(FileNode))
            {
                List<String> StreamFileNames = new List<String>(FileNodeMap.GetStreamFileNames(FileNode));
                foreach (String StreamFileName in StreamFileNames)
                {
                    FileNode StreamNode = FileNodeMap.Get(StreamFileName);
                    if (null == StreamNode)
                        continue; /* should not happen */
                    FileNodeMap.Remove(StreamNode);
                }
                FileNodeMap.Remove(FileNode);
            }
        }

        public override void Close(
            Object FileNode0,
            Object FileDesc)
        {
            FileNode FileNode = (FileNode)FileNode0;
            Interlocked.Decrement(ref FileNode.OpenCount);
        }

#if MEMFS_SLOWIO
        private UInt64 Hash(UInt64 X)
        {
            X = (X ^ (X >> 30)) * 0xbf58476d1ce4e5b9ul;
            X = (X ^ (X >> 27)) * 0x94d049bb133111ebul;
            X = X ^ (X >> 31);
            return X;
        }

        private static int Spin = 0;

        private UInt64 PseudoRandom(UInt64 To)
        {
            /* John Oberschelp's PRNG */
            Interlocked.Increment(ref Spin);
            return Hash((UInt64)Spin) % To;
        }

        private bool SlowioReturnPending()
        {
            if (0 == SlowioMaxDelay)
            {
                return false;
            }
            return PseudoRandom(100) < SlowioPercentDelay;
        }

        private void SlowioSnooze()
        {
            double Millis = PseudoRandom(SlowioMaxDelay + 1) >> (int) PseudoRandom(SlowioRarefyDelay + 1);
            Thread.Sleep(TimeSpan.FromMilliseconds(Millis));
        }

        private void SlowioReadTask(
            Object FileNode0,
            IntPtr Buffer,
            UInt64 Offset,
            UInt64 EndOffset,
            UInt64 RequestHint)
        {
            SlowioSnooze();

            UInt32 BytesTransferred = (UInt32)(EndOffset - Offset);
            FileNode FileNode = (FileNode)FileNode0;
            Marshal.Copy(FileNode.FileData, (int)Offset, Buffer, (int)BytesTransferred);

            Host.SendReadResponse(RequestHint, STATUS_SUCCESS, BytesTransferred);
            Interlocked.Decrement(ref SlowioTasksRunning);
        }

        private void SlowioWriteTask(
            Object FileNode0,
            IntPtr Buffer,
            UInt64 Offset,
            UInt64 EndOffset,
            UInt64 RequestHint)
        {
            SlowioSnooze();

            UInt32 BytesTransferred = (UInt32)(EndOffset - Offset);
            FileNode FileNode = (FileNode)FileNode0;
            FileInfo FileInfo = FileNode.GetFileInfo();
            Marshal.Copy(Buffer, FileNode.FileData, (int)Offset, (int)BytesTransferred);

            Host.SendWriteResponse(RequestHint, STATUS_SUCCESS, BytesTransferred, ref FileInfo);
            Interlocked.Decrement(ref SlowioTasksRunning);
        }

        private void SlowioReadDirectoryTask(
            Object FileNode0,
            Object FileDesc,
            String Pattern,
            String Marker,
            IntPtr Buffer,
            UInt32 Length,
            UInt64 RequestHint)
        {
            SlowioSnooze();

            UInt32 BytesTransferred;
            var Status = SeekableReadDirectory(FileNode0, FileDesc, Pattern, Marker, Buffer, Length, out BytesTransferred);

            Host.SendReadDirectoryResponse(RequestHint, Status, BytesTransferred);
            Interlocked.Decrement(ref SlowioTasksRunning);
        }
#endif

        public override Int32 Read(
            Object FileNode0,
            Object FileDesc,
            IntPtr Buffer,
            UInt64 Offset,
            UInt32 Length,
            out UInt32 BytesTransferred)
        {
            FileNode FileNode = (FileNode)FileNode0;
            UInt64 EndOffset;

            if (Offset >= FileNode.FileInfo.FileSize)
            {
                BytesTransferred = default(UInt32);
                return STATUS_END_OF_FILE;
            }

            EndOffset = Offset + Length;
            if (EndOffset > FileNode.FileInfo.FileSize)
                EndOffset = FileNode.FileInfo.FileSize;

#if MEMFS_SLOWIO
            if (SlowioReturnPending())
            {
                var Hint = Host.GetOperationRequestHint();
                try
                {
                    Interlocked.Increment(ref SlowioTasksRunning);
                    Task.Run(() => SlowioReadTask(FileNode0, Buffer, Offset, EndOffset, Hint)).ConfigureAwait(false);

                    BytesTransferred = 0;
                    return STATUS_PENDING;
                }
                catch (Exception)
                {
                    Interlocked.Decrement(ref SlowioTasksRunning);
                }
            }
#endif

            BytesTransferred = (UInt32)(EndOffset - Offset);
            Marshal.Copy(FileNode.FileData, (int)Offset, Buffer, (int)BytesTransferred);

            return STATUS_SUCCESS;
        }

        public override Int32 Write(
            Object FileNode0,
            Object FileDesc,
            IntPtr Buffer,
            UInt64 Offset,
            UInt32 Length,
            Boolean WriteToEndOfFile,
            Boolean ConstrainedIo,
            out UInt32 BytesTransferred,
            out FileInfo FileInfo)
        {
            FileNode FileNode = (FileNode)FileNode0;
            UInt64 EndOffset;

            if (ConstrainedIo)
            {
                if (Offset >= FileNode.FileInfo.FileSize)
                {
                    BytesTransferred = default(UInt32);
                    FileInfo = default(FileInfo);
                    return STATUS_SUCCESS;
                }
                EndOffset = Offset + Length;
                if (EndOffset > FileNode.FileInfo.FileSize)
                    EndOffset = FileNode.FileInfo.FileSize;
            }
            else
            {
                if (WriteToEndOfFile)
                    Offset = FileNode.FileInfo.FileSize;
                EndOffset = Offset + Length;
                if (EndOffset > FileNode.FileInfo.FileSize)
                {
                    Int32 Result = SetFileSizeInternal(FileNode, EndOffset, false);
                    if (0 > Result)
                    {
                        BytesTransferred = default(UInt32);
                        FileInfo = default(FileInfo);
                        return Result;
                    }
                }
            }

#if MEMFS_SLOWIO
            if (SlowioReturnPending())
            {
                var hint = Host.GetOperationRequestHint();
                try
                {
                    Interlocked.Increment(ref SlowioTasksRunning);
                    Task.Run(() => SlowioWriteTask(FileNode0, Buffer, Offset, EndOffset, hint)).ConfigureAwait(false);

                    BytesTransferred = 0;
                    FileInfo = default(FileInfo);
                    return STATUS_PENDING;
                }
                catch (Exception)
                {
                    Interlocked.Decrement(ref SlowioTasksRunning);
                }
            }
#endif

            BytesTransferred = (UInt32)(EndOffset - Offset);
            Marshal.Copy(Buffer, FileNode.FileData, (int)Offset, (int)BytesTransferred);

            FileInfo = FileNode.GetFileInfo();

            return STATUS_SUCCESS;
        }

        public override Int32 Flush(
            Object FileNode0,
            Object FileDesc,
            out FileInfo FileInfo)
        {
            FileNode FileNode = (FileNode)FileNode0;

            /*  nothing to flush, since we do not cache anything */
            FileInfo = null != FileNode ? FileNode.GetFileInfo() : default(FileInfo);

            return STATUS_SUCCESS;
        }

        public override Int32 GetFileInfo(
            Object FileNode0,
            Object FileDesc,
            out FileInfo FileInfo)
        {
            FileNode FileNode = (FileNode)FileNode0;

            FileInfo = FileNode.GetFileInfo();

            return STATUS_SUCCESS;
        }

        public override Int32 SetBasicInfo(
            Object FileNode0,
            Object FileDesc,
            UInt32 FileAttributes,
            UInt64 CreationTime,
            UInt64 LastAccessTime,
            UInt64 LastWriteTime,
            UInt64 ChangeTime,
            out FileInfo FileInfo)
        {
            FileNode FileNode = (FileNode)FileNode0;

            if (null != FileNode.MainFileNode)
                FileNode = FileNode.MainFileNode;

            if (unchecked((UInt32)(-1)) != FileAttributes)
                FileNode.FileInfo.FileAttributes = FileAttributes;
            if (0 != CreationTime)
                FileNode.FileInfo.CreationTime = CreationTime;
            if (0 != LastAccessTime)
                FileNode.FileInfo.LastAccessTime = LastAccessTime;
            if (0 != LastWriteTime)
                FileNode.FileInfo.LastWriteTime = LastWriteTime;
            if (0 != ChangeTime)
                FileNode.FileInfo.ChangeTime = ChangeTime;

            FileInfo = FileNode.GetFileInfo();

            return STATUS_SUCCESS;
        }

        public override Int32 SetFileSize(
            Object FileNode0,
            Object FileDesc,
            UInt64 NewSize,
            Boolean SetAllocationSize,
            out FileInfo FileInfo)
        {
            FileNode FileNode = (FileNode)FileNode0;
            Int32 Result;

            Result = SetFileSizeInternal(FileNode, NewSize, SetAllocationSize);
            FileInfo = 0 <= Result ? FileNode.GetFileInfo() : default(FileInfo);

            return STATUS_SUCCESS;
        }

        private Int32 SetFileSizeInternal(
            FileNode FileNode,
            UInt64 NewSize,
            Boolean SetAllocationSize)
        {
            if (SetAllocationSize)
            {
                if (FileNode.FileInfo.AllocationSize != NewSize)
                {
                    if (NewSize > MaxFileSize)
                        return STATUS_DISK_FULL;

                    byte[] FileData = null;
                    if (0 != NewSize)
                        try
                        {
                            FileData = new byte[NewSize];
                        }
                        catch
                        {
                            return STATUS_INSUFFICIENT_RESOURCES;
                        }
                    int CopyLength = (int)Math.Min(FileNode.FileInfo.AllocationSize, NewSize);
                    if (0 != CopyLength)
                        Array.Copy(FileNode.FileData, FileData, CopyLength);

                    FileNode.FileData = FileData;
                    FileNode.FileInfo.AllocationSize = NewSize;
                    if (FileNode.FileInfo.FileSize > NewSize)
                        FileNode.FileInfo.FileSize = NewSize;
                }
            }
            else
            {
                if (FileNode.FileInfo.FileSize != NewSize)
                {
                    if (FileNode.FileInfo.AllocationSize < NewSize)
                    {
                        UInt64 AllocationUnit = MEMFS_SECTOR_SIZE * MEMFS_SECTORS_PER_ALLOCATION_UNIT;
                        UInt64 AllocationSize = (NewSize + AllocationUnit - 1) / AllocationUnit * AllocationUnit;
                        Int32 Result = SetFileSizeInternal(FileNode, AllocationSize, true);
                        if (0 > Result)
                            return Result;
                    }

                    if (FileNode.FileInfo.FileSize < NewSize)
                    {
                        int CopyLength = (int)(NewSize - FileNode.FileInfo.FileSize);
                        if (0 != CopyLength)
                            Array.Clear(FileNode.FileData, (int)FileNode.FileInfo.FileSize, CopyLength);
                    }
                    FileNode.FileInfo.FileSize = NewSize;
                }
            }

            return STATUS_SUCCESS;
        }

        public override Int32 CanDelete(
            Object FileNode0,
            Object FileDesc,
            String FileName)
        {
            FileNode FileNode = (FileNode)FileNode0;

            if (FileNodeMap.HasChild(FileNode))
                return STATUS_DIRECTORY_NOT_EMPTY;

            return STATUS_SUCCESS;
        }

        public override Int32 Rename(
            Object FileNode0,
            Object FileDesc,
            String FileName,
            String NewFileName,
            Boolean ReplaceIfExists)
        {
            FileNode FileNode = (FileNode)FileNode0;
            FileNode NewFileNode;

            NewFileNode = FileNodeMap.Get(NewFileName);
            if (null != NewFileNode && FileNode != NewFileNode)
            {
                if (!ReplaceIfExists)
                    return STATUS_OBJECT_NAME_COLLISION;
                if (0 != (NewFileNode.FileInfo.FileAttributes & (UInt32)FileAttributes.Directory))
                    return STATUS_ACCESS_DENIED;
            }

            if (null != NewFileNode && FileNode != NewFileNode)
                FileNodeMap.Remove(NewFileNode);

            List<String> DescendantFileNames = new List<String>(FileNodeMap.GetDescendantFileNames(FileNode));
            foreach (String DescendantFileName in DescendantFileNames)
            {
                FileNode DescendantFileNode = FileNodeMap.Get(DescendantFileName);
                if (null == DescendantFileNode)
                    continue; /* should not happen */
                FileNodeMap.Remove(DescendantFileNode);
                DescendantFileNode.FileName =
                    NewFileName + DescendantFileNode.FileName.Substring(FileName.Length);
                FileNodeMap.Insert(DescendantFileNode);
            }

            return STATUS_SUCCESS;
        }

        public override Int32 GetSecurity(
            Object FileNode0,
            Object FileDesc,
            ref Byte[] SecurityDescriptor)
        {
            FileNode FileNode = (FileNode)FileNode0;

            if (null != FileNode.MainFileNode)
                FileNode = FileNode.MainFileNode;

            SecurityDescriptor = FileNode.FileSecurity;

            return STATUS_SUCCESS;
        }

        public override Int32 SetSecurity(
            Object FileNode0,
            Object FileDesc,
            AccessControlSections Sections,
            Byte[] SecurityDescriptor)
        {
            FileNode FileNode = (FileNode)FileNode0;

            if (null != FileNode.MainFileNode)
                FileNode = FileNode.MainFileNode;

            return ModifySecurityDescriptorEx(FileNode.FileSecurity, Sections, SecurityDescriptor,
                ref FileNode.FileSecurity);
        }

        public override Boolean ReadDirectoryEntry(
            Object FileNode0,
            Object FileDesc,
            String Pattern,
            String Marker,
            ref Object Context,
            out String FileName,
            out FileInfo FileInfo)
        {
            FileNode FileNode = (FileNode)FileNode0;
            IEnumerator<String> Enumerator = (IEnumerator<String>)Context;

            if (null == Enumerator)
            {
                List<String> ChildrenFileNames = new List<String>();
                if ("\\" != FileNode.FileName)
                {
                    /* if this is not the root directory add the dot entries */
                    if (null == Marker)
                        ChildrenFileNames.Add(".");
                    if (null == Marker || "." == Marker)
                        ChildrenFileNames.Add("..");
                }
                ChildrenFileNames.AddRange(FileNodeMap.GetChildrenFileNames(FileNode,
                    "." != Marker && ".." != Marker ? Marker : null));
                Context = Enumerator = ChildrenFileNames.GetEnumerator();
            }

            while (Enumerator.MoveNext())
            {
                String FullFileName = Enumerator.Current;
                if ("." == FullFileName)
                {
                    FileName = ".";
                    FileInfo = FileNode.GetFileInfo();
                    return true;
                }
                else if (".." == FullFileName)
                {
                    Int32 Result = STATUS_SUCCESS;
                    FileNode ParentNode = FileNodeMap.GetParent(FileNode.FileName, ref Result);
                    if (null != ParentNode)
                    {
                        FileName = "..";
                        FileInfo = ParentNode.GetFileInfo();
                        return true;
                    }
                }
                else
                {
                    FileNode ChildFileNode = FileNodeMap.Get(FullFileName);
                    if (null != ChildFileNode)
                    {
                        FileName = Path.GetFileName(FullFileName);
                        FileInfo = ChildFileNode.GetFileInfo();
                        return true;
                    }
                }
            }

            FileName = default(String);
            FileInfo = default(FileInfo);
            return false;
        }

#if MEMFS_SLOWIO
        public override int ReadDirectory(
            Object FileNode0,
            Object FileDesc,
            String Pattern,
            String Marker,
            IntPtr Buffer,
            UInt32 Length,
            out UInt32 BytesTransferred)
        {
            if (SlowioReturnPending())
            {
                var Hint = Host.GetOperationRequestHint();
                try
                {
                    Interlocked.Increment(ref SlowioTasksRunning);
                    Task.Run(() => SlowioReadDirectoryTask(FileNode0, FileDesc, Pattern, Marker, Buffer, Length, Hint));
                    BytesTransferred = 0;

                    return STATUS_PENDING;
                }
                catch (Exception)
                {
                    Interlocked.Decrement(ref SlowioTasksRunning);
                }
            }

            return SeekableReadDirectory(FileNode0, FileDesc, Pattern, Marker, Buffer, Length, out BytesTransferred);
        }
#endif

        public override int GetDirInfoByName(
            Object ParentNode0,
            Object FileDesc,
            String FileName,
            out String NormalizedName,
            out FileInfo FileInfo)
        {
            FileNode ParentNode = (FileNode)ParentNode0;
            FileNode FileNode;

            FileName =
                ParentNode.FileName +
                ("\\" == ParentNode.FileName ? "" : "\\") +
                Path.GetFileName(FileName);

            FileNode = FileNodeMap.Get(FileName);
            if (null == FileNode)
            {
                NormalizedName = default(String);
                FileInfo = default(FileInfo);
                return STATUS_OBJECT_NAME_NOT_FOUND;
            }

            NormalizedName = Path.GetFileName(FileNode.FileName);
            FileInfo = FileNode.FileInfo;

            return STATUS_SUCCESS;
        }

        public override Int32 GetReparsePointByName(
            String FileName,
            Boolean IsDirectory,
            ref Byte[] ReparseData)
        {
            FileNode FileNode;

            FileNode = FileNodeMap.Get(FileName);
            if (null == FileNode)
                return STATUS_OBJECT_NAME_NOT_FOUND;

            if (0 == (FileNode.FileInfo.FileAttributes & (UInt32)FileAttributes.ReparsePoint))
                return STATUS_NOT_A_REPARSE_POINT;

            ReparseData = FileNode.ReparseData;

            return STATUS_SUCCESS;
        }

        public override Int32 GetReparsePoint(
            Object FileNode0,
            Object FileDesc,
            String FileName,
            ref Byte[] ReparseData)
        {
            FileNode FileNode = (FileNode)FileNode0;

            if (null != FileNode.MainFileNode)
                FileNode = FileNode.MainFileNode;

            if (0 == (FileNode.FileInfo.FileAttributes & (UInt32)FileAttributes.ReparsePoint))
                return STATUS_NOT_A_REPARSE_POINT;

            ReparseData = FileNode.ReparseData;

            return STATUS_SUCCESS;
        }

        public override Int32 SetReparsePoint(
            Object FileNode0,
            Object FileDesc,
            String FileName,
            Byte[] ReparseData)
        {
            FileNode FileNode = (FileNode)FileNode0;

            if (null != FileNode.MainFileNode)
                FileNode = FileNode.MainFileNode;

            if (FileNodeMap.HasChild(FileNode))
                return STATUS_DIRECTORY_NOT_EMPTY;

            if (null != FileNode.ReparseData)
            {
                Int32 Result = CanReplaceReparsePoint(FileNode.ReparseData, ReparseData);
                if (0 > Result)
                    return Result;
            }

            FileNode.FileInfo.FileAttributes |= (UInt32)FileAttributes.ReparsePoint;
            FileNode.FileInfo.ReparseTag = GetReparseTag(ReparseData);
            FileNode.ReparseData = ReparseData;

            return STATUS_SUCCESS;
        }

        public override Int32 DeleteReparsePoint(
            Object FileNode0,
            Object FileDesc,
            String FileName,
            Byte[] ReparseData)
        {
            FileNode FileNode = (FileNode)FileNode0;

            if (null != FileNode.MainFileNode)
                FileNode = FileNode.MainFileNode;

            if (null != FileNode.ReparseData)
            {
                Int32 Result = CanReplaceReparsePoint(FileNode.ReparseData, ReparseData);
                if (0 > Result)
                    return Result;
            }
            else
                return STATUS_NOT_A_REPARSE_POINT;

            FileNode.FileInfo.FileAttributes &= ~(UInt32)FileAttributes.ReparsePoint;
            FileNode.FileInfo.ReparseTag = 0;
            FileNode.ReparseData = null;

            return STATUS_SUCCESS;
        }

        public override Boolean GetStreamEntry(
            Object FileNode0,
            Object FileDesc,
            ref Object Context,
            out String StreamName,
            out UInt64 StreamSize,
            out UInt64 StreamAllocationSize)
        {
            FileNode FileNode = (FileNode)FileNode0;
            IEnumerator<String> Enumerator = (IEnumerator<String>)Context;

            if (null == Enumerator)
            {
                if (null != FileNode.MainFileNode)
                    FileNode = FileNode.MainFileNode;

                List<String> StreamFileNames = new List<String>();
                if (0 == (FileNode.FileInfo.FileAttributes & (UInt32)FileAttributes.Directory))
                    StreamFileNames.Add(FileNode.FileName);
                StreamFileNames.AddRange(FileNodeMap.GetStreamFileNames(FileNode));
                Context = Enumerator = StreamFileNames.GetEnumerator();
            }

            while (Enumerator.MoveNext())
            {
                String FullFileName = Enumerator.Current;
                FileNode StreamFileNode = FileNodeMap.Get(FullFileName);
                if (null != StreamFileNode)
                {
                    int Index = FullFileName.IndexOf(':');
                    if (0 > Index)
                        StreamName = "";
                    else
                        StreamName = FullFileName.Substring(Index + 1);
                    StreamSize = StreamFileNode.FileInfo.FileSize;
                    StreamAllocationSize = StreamFileNode.FileInfo.AllocationSize;
                    return true;
                }
            }

            StreamName = default(String);
            StreamSize = default(UInt64);
            StreamAllocationSize = default(UInt64);
            return false;
        }
        public override Boolean GetEaEntry(
            Object FileNode0,
            Object FileDesc,
            ref Object Context,
            out String EaName,
            out Byte[] EaValue,
            out Boolean NeedEa)
        {
            FileNode FileNode = (FileNode)FileNode0;
            IEnumerator<KeyValuePair<String, EaValueData>> Enumerator =
                (IEnumerator<KeyValuePair<String, EaValueData>>)Context;

            if (null == Enumerator)
            {
                SortedDictionary<String, EaValueData> EaMap = FileNode.GetEaMap(false);
                if (null == EaMap)
                {
                    EaName = default(String);
                    EaValue = default(Byte[]);
                    NeedEa = default(Boolean);
                    return false;
                }

                Context = Enumerator = EaMap.GetEnumerator();
            }

            while (Enumerator.MoveNext())
            {
                KeyValuePair<String, EaValueData> Pair = Enumerator.Current;
                EaName = Pair.Key;
                EaValue = Pair.Value.EaValue;
                NeedEa = Pair.Value.NeedEa;
                return true;
            }

            EaName = default(String);
            EaValue = default(Byte[]);
            NeedEa = default(Boolean);
            return false;
        }
        public override Int32 SetEaEntry(
            Object FileNode0,
            Object FileDesc,
            ref Object Context,
            String EaName,
            Byte[] EaValue,
            Boolean NeedEa)
        {
            FileNode FileNode = (FileNode)FileNode0;
            SortedDictionary<String, EaValueData> EaMap = FileNode.GetEaMap(true);
            EaValueData Data;
            UInt32 EaSizePlus = 0, EaSizeMinus = 0;
            if (EaMap.TryGetValue(EaName, out Data))
            {
                EaSizeMinus = GetEaEntrySize(EaName, Data.EaValue, Data.NeedEa);
                EaMap.Remove(EaName);
            }
            if (null != EaValue)
            {
                Data.EaValue = EaValue;
                Data.NeedEa = NeedEa;
                EaMap[EaName] = Data;
                EaSizePlus = GetEaEntrySize(EaName, EaValue, NeedEa);
            }
            FileNode.FileInfo.EaSize = FileNode.FileInfo.EaSize + EaSizePlus - EaSizeMinus;
            return STATUS_SUCCESS;
        }

        private FileNodeMap FileNodeMap;
        private UInt32 MaxFileNodes;
        private UInt32 MaxFileSize;
        private UInt64 SlowioMaxDelay;
        private UInt64 SlowioPercentDelay;
        private UInt64 SlowioRarefyDelay;
        private volatile Int32 SlowioTasksRunning;
        private String VolumeLabel;
    }

    class MemfsService : Service
    {
        private class CommandLineUsageException : Exception
        {
            public CommandLineUsageException(String Message = null) : base(Message)
            {
                HasMessage = null != Message;
            }

            public bool HasMessage;
        }

        private const String PROGNAME = "memfs-dotnet";

        public MemfsService() : base("MemfsService")
        {
        }
        protected override void OnStart(String[] Args)
        {
            try
            {
                Boolean CaseInsensitive = false;
                String DebugLogFile = null;
                UInt32 DebugFlags = 0;
                UInt32 FileInfoTimeout = unchecked((UInt32)(-1));
                UInt32 MaxFileNodes = 1024;
                UInt32 MaxFileSize = 16 * 1024 * 1024;
                UInt32 SlowioMaxDelay = 0;
                UInt32 SlowioPercentDelay = 0;
                UInt32 SlowioRarefyDelay = 0;
                String FileSystemName = null;
                String VolumePrefix = null;
                String MountPoint = null;
                String RootSddl = null;
                FileSystemHost Host = null;
                Memfs Memfs = null;
                int I;

                for (I = 1; Args.Length > I; I++)
                {
                    String Arg = Args[I];
                    if ('-' != Arg[0])
                        break;
                    switch (Arg[1])
                    {
                    case '?':
                        throw new CommandLineUsageException();
                    case 'D':
                        argtos(Args, ref I, ref DebugLogFile);
                        break;
                    case 'd':
                        argtol(Args, ref I, ref DebugFlags);
                        break;
                    case 'F':
                        argtos(Args, ref I, ref FileSystemName);
                        break;
                    case 'i':
                        CaseInsensitive = true;
                        break;
                    case 'm':
                        argtos(Args, ref I, ref MountPoint);
                        break;
                    case 'M':
                        argtol(Args, ref I, ref SlowioMaxDelay);
                        break;
                    case 'n':
                        argtol(Args, ref I, ref MaxFileNodes);
                        break;
                    case 'P':
                        argtol(Args, ref I, ref SlowioPercentDelay);
                        break;
                    case 'R':
                       argtol(Args, ref I, ref SlowioRarefyDelay);
                       break;
                    case 'S':
                        argtos(Args, ref I, ref RootSddl);
                        break;
                    case 's':
                        argtol(Args, ref I, ref MaxFileSize);
                        break;
                    case 't':
                        argtol(Args, ref I, ref FileInfoTimeout);
                        break;
                    case 'u':
                        argtos(Args, ref I, ref VolumePrefix);
                        break;
                    default:
                        throw new CommandLineUsageException();
                    }
                }

                if (Args.Length > I)
                    throw new CommandLineUsageException();

                if ((null == VolumePrefix || 0 == VolumePrefix.Length) && null == MountPoint)
                    throw new CommandLineUsageException();

                if (null != DebugLogFile)
                    if (0 > FileSystemHost.SetDebugLogFile(DebugLogFile))
                        throw new CommandLineUsageException("cannot open debug log file");

                Host = new FileSystemHost(Memfs = new Memfs(
                    CaseInsensitive, MaxFileNodes, MaxFileSize, RootSddl,
                    SlowioMaxDelay, SlowioPercentDelay, SlowioRarefyDelay));
                Host.FileInfoTimeout = FileInfoTimeout;
                Host.Prefix = VolumePrefix;
                Host.FileSystemName = null != FileSystemName ? FileSystemName : "-MEMFS";
                if (0 > Host.Mount(MountPoint, null, false, DebugFlags))
                    throw new IOException("cannot mount file system");
                MountPoint = Host.MountPoint();
                _Host = Host;

                Log(EVENTLOG_INFORMATION_TYPE, String.Format("{0} -t {1} -n {2} -s {3}{4}{5}{6}{7}{8}{9}",
                    PROGNAME, (Int32)FileInfoTimeout, MaxFileNodes, MaxFileSize,
                    null != RootSddl ? " -S " : "", null != RootSddl ? RootSddl : "",
                    null != VolumePrefix && 0 < VolumePrefix.Length ? " -u " : "",
                        null != VolumePrefix && 0 < VolumePrefix.Length ? VolumePrefix : "",
                    null != MountPoint ? " -m " : "", null != MountPoint ? MountPoint : ""));
            }
            catch (CommandLineUsageException ex)
            {
                Log(EVENTLOG_ERROR_TYPE, String.Format(
                    "{0}" +
                    "usage: {1} OPTIONS\n" +
                    "\n" +
                    "options:\n" +
                    "    -d DebugFlags       [-1: enable all debug logs]\n" +
                    "    -D DebugLogFile     [file path; use - for stderr]\n" +
                    "    -i                  [case insensitive file system]\n" +
                    "    -t FileInfoTimeout  [millis]\n" +
                    "    -n MaxFileNodes\n" +
                    "    -s MaxFileSize      [bytes]\n" +
                    "    -M MaxDelay         [maximum slow IO delay in millis]\n" +
                    "    -P PercentDelay     [percent of slow IO to make pending]\n" +
                    "    -R RarefyDelay      [adjust the rarity of pending slow IO]\n" +
                    "    -F FileSystemName\n" +
                    "    -S RootSddl         [file rights: FA, etc; NO generic rights: GA, etc.]\n" +
                    "    -u \\Server\\Share    [UNC prefix (single backslash)]\n" +
                    "    -m MountPoint       [X:|* (required if no UNC prefix)]\n",
                    ex.HasMessage ? ex.Message + "\n" : "",
                    PROGNAME));
                throw;
            }
            catch (Exception ex)
            {
                Log(EVENTLOG_ERROR_TYPE, String.Format("{0}", ex.Message));
                throw;
            }
        }
        protected override void OnStop()
        {
            _Host.Unmount();
            _Host = null;
        }

        private static void argtos(String[] Args, ref int I, ref String V)
        {
            if (Args.Length > ++I)
                V = Args[I];
            else
                throw new CommandLineUsageException();
        }
        private static void argtol(String[] Args, ref int I, ref UInt32 V)
        {
            Int32 R;
            if (Args.Length > ++I)
                V = Int32.TryParse(Args[I], out R) ? (UInt32)R : V;
            else
                throw new CommandLineUsageException();
        }

        private FileSystemHost _Host;
    }

    class Program
    {
        static void Main(string[] args)
        {
            Environment.ExitCode = new MemfsService().Run();
        }
    }
}
