mirror of
				https://github.com/winfsp/winfsp.git
				synced 2025-10-31 12:08:41 -05:00 
			
		
		
		
	- Rename PostDispositionForDirOnly to PostDispositionWhenNecessaryOnly - Implement PostDispositionWhenNecessaryOnly across the board
		
			
				
	
	
		
			1528 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			1528 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| /**
 | |
|  * @file Program.cs
 | |
|  *
 | |
|  * @copyright 2015-2022 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();
 | |
|         }
 | |
|     }
 | |
| }
 |