/** * @file Program.cs * * @copyright 2015-2018 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 file in * accordance with the commercial license agreement provided with the * software. */ using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Threading; 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); } } 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; } } private static UInt64 IndexNumber = 1; public String FileName; public FileInfo FileInfo; public Byte[] FileSecurity; public Byte[] FileData; public Byte[] ReparseData; 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(Comparer); Map = new Dictionary(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 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 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 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 Set; private Dictionary Map; } class Memfs : FileSystemBase { 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) { this.FileNodeMap = new FileNodeMap(CaseInsensitive); this.MaxFileNodes = MaxFileNodes; this.MaxFileSize = MaxFileSize; /* * 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) { FileSystemHost 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.PassQueryDirectoryFileName = true; return STATUS_SUCCESS; } 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 Create( String FileName, UInt32 CreateOptions, UInt32 GrantedAccess, UInt32 FileAttributes, Byte[] SecurityDescriptor, UInt64 AllocationSize, 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 (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; } Interlocked.Increment(ref FileNode.OpenCount); FileNode0 = FileNode; FileInfo = FileNode.GetFileInfo(); NormalizedName = FileNode.FileName; return STATUS_SUCCESS; } public override Int32 Overwrite( Object FileNode0, Object FileDesc, UInt32 FileAttributes, Boolean ReplaceFileAttributes, UInt64 AllocationSize, out FileInfo FileInfo) { FileInfo = default(FileInfo); FileNode FileNode = (FileNode)FileNode0; Int32 Result; List StreamFileNames = new List(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); } 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 StreamFileNames = new List(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); } 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; 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; } } } 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 DescendantFileNames = new List(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; FileNode.FileSecurity = ModifySecurityDescriptor( FileNode.FileSecurity, Sections, SecurityDescriptor); return STATUS_SUCCESS; } 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 Enumerator = (IEnumerator)Context; if (null == Enumerator) { List ChildrenFileNames = new List(); 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; } 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 Enumerator = (IEnumerator)Context; if (null == Enumerator) { if (null != FileNode.MainFileNode) FileNode = FileNode.MainFileNode; List StreamFileNames = new List(); 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; } private FileNodeMap FileNodeMap; private UInt32 MaxFileNodes; private UInt32 MaxFileSize; 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; 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 'n': argtol(Args, ref I, ref MaxFileNodes); 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)); 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" + " -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(); } } }