From 40052b143e7bd7acfeb6c3a2895a6520cfaaf03f Mon Sep 17 00:00:00 2001 From: Bill Zissimopoulos Date: Fri, 18 Dec 2020 16:39:30 -0800 Subject: [PATCH] tst: notifyfs-dotnet: add .NET file system to demo file notification mechanism --- tst/notifyfs-dotnet/.gitignore | 9 + tst/notifyfs-dotnet/Program.cs | 394 +++++++++++++++++++++ tst/notifyfs-dotnet/notifyfs-dotnet.csproj | 77 ++++ tst/notifyfs-dotnet/notifyfs-dotnet.sln | 25 ++ 4 files changed, 505 insertions(+) create mode 100644 tst/notifyfs-dotnet/.gitignore create mode 100644 tst/notifyfs-dotnet/Program.cs create mode 100644 tst/notifyfs-dotnet/notifyfs-dotnet.csproj create mode 100644 tst/notifyfs-dotnet/notifyfs-dotnet.sln diff --git a/tst/notifyfs-dotnet/.gitignore b/tst/notifyfs-dotnet/.gitignore new file mode 100644 index 00000000..b38c7927 --- /dev/null +++ b/tst/notifyfs-dotnet/.gitignore @@ -0,0 +1,9 @@ +build +*.ncb +*.suo +*.vcproj.* +*.vcxproj.user +*.csproj.user +*.VC.db +*.VC.opendb +.vs diff --git a/tst/notifyfs-dotnet/Program.cs b/tst/notifyfs-dotnet/Program.cs new file mode 100644 index 00000000..d0572d02 --- /dev/null +++ b/tst/notifyfs-dotnet/Program.cs @@ -0,0 +1,394 @@ +/** + * @file Program.cs + * + * @copyright 2015-2020 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. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Text; +using System.Threading; + +using Fsp; +using VolumeInfo = Fsp.Interop.VolumeInfo; +using FileInfo = Fsp.Interop.FileInfo; +using NotifyInfo = Fsp.Interop.NotifyInfo; +using NotifyAction = Fsp.Interop.NotifyAction; +using NotifyFilter = Fsp.Interop.NotifyFilter; + +namespace notifyfs +{ + class Notifyfs : FileSystemBase + { + public override Int32 Init(Object Host) + { + _Host = (FileSystemHost)Host; + _Host.SectorSize = ALLOCATION_UNIT; + _Host.SectorsPerAllocationUnit = 1; + _Host.FileInfoTimeout = 1000; + _Host.CaseSensitiveSearch = false; + _Host.CasePreservedNames = true; + _Host.UnicodeOnDisk = true; + _Host.PersistentAcls = false; + _Host.PostCleanupWhenModifiedOnly = true; + _Host.VolumeCreationTime = 0; + _Host.VolumeSerialNumber = 0; + + return STATUS_SUCCESS; + } + public override Int32 Mounted(Object Host) + { + _Timer = new Timer(this.Tick, null, 0, 1000); + + return STATUS_SUCCESS; + } + public override void Unmounted(Object Host) + { + WaitHandle Event = new ManualResetEvent(false); + _Timer.Dispose(Event); + Event.WaitOne(); + } + public override Int32 GetVolumeInfo( + out VolumeInfo VolumeInfo) + { + VolumeInfo = default(VolumeInfo); + + return STATUS_SUCCESS; + } + public override Int32 GetSecurityByName( + String FileName, + out UInt32 FileAttributes/* or ReparsePointIndex */, + ref Byte[] SecurityDescriptor) + { + int Index = FileLookup(FileName); + if (-1 == Index) + { + FileAttributes = default(UInt32); + return STATUS_OBJECT_NAME_NOT_FOUND; + } + + FileAttributes = 0 == Index ? (UInt32)System.IO.FileAttributes.Directory : 0; + if (null != SecurityDescriptor) + SecurityDescriptor = DefaultSecurity; + + return STATUS_SUCCESS; + } + public override Int32 Open( + String FileName, + UInt32 CreateOptions, + UInt32 GrantedAccess, + out Object FileNode, + out Object FileDesc, + out FileInfo FileInfo, + out String NormalizedName) + { + FileNode = default(Object); + FileDesc = default(Object); + FileInfo = default(FileInfo); + NormalizedName = default(String); + + int Index = FileLookup(FileName); + if (-1 == Index) + return STATUS_OBJECT_NAME_NOT_FOUND; + + FileNode = Index; + FillFileInfo(Index, out FileInfo); + + return STATUS_SUCCESS; + } + public override Int32 Read( + Object FileNode, + Object FileDesc, + IntPtr Buffer, + UInt64 Offset, + UInt32 Length, + out UInt32 BytesTransferred) + { + int Index = (int)FileNode; + UInt64 EndOffset; + Byte[] Contents = FileContents(Index); + + if (Offset >= (UInt64)Contents.Length) + { + BytesTransferred = 0; + return STATUS_END_OF_FILE; + } + + EndOffset = Offset + Length; + if (EndOffset > (UInt64)Contents.Length) + EndOffset = (UInt64)Contents.Length; + + BytesTransferred = (UInt32)(EndOffset - Offset); + Marshal.Copy(Contents, (int)Offset, Buffer, (int)BytesTransferred); + + return STATUS_SUCCESS; + } + public override Int32 GetFileInfo( + Object FileNode, + Object FileDesc, + out FileInfo FileInfo) + { + int Index = (int)FileNode; + + FillFileInfo(Index, out FileInfo); + + return STATUS_SUCCESS; + } + public override Boolean ReadDirectoryEntry( + Object FileNode, + Object FileDesc, + String Pattern, + String Marker, + ref Object Context, + out String FileName, + out FileInfo FileInfo) + { + IEnumerator Enumerator = (IEnumerator)Context; + + if (null == Enumerator) + { + List ChildrenFileNames = new List(); + for (int Index = 1, Count = FileCount(); Count >= Index; Index++) + ChildrenFileNames.Add(String.Format("{0}", Index)); + Context = Enumerator = ChildrenFileNames.GetEnumerator(); + } + + while (Enumerator.MoveNext()) + { + FileName = Enumerator.Current; + FillFileInfo(int.Parse(FileName), out FileInfo); + return true; + } + + FileName = default(String); + FileInfo = default(FileInfo); + return false; + } + + private static int CountFromTicks(int Ticks) + { + /* + * The formula below produces the periodic sequence: + * 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, + * 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, + * ... + */ + int div10 = (Ticks % 20) / 10; + int mod10 = Ticks % 10; + int mdv10 = 1 - div10; + int mmd10 = 10 - mod10; + return mdv10 * mod10 + div10 * mmd10; + } + private int FileCount() + { + int Ticks = Thread.VolatileRead(ref _Ticks); + return CountFromTicks(Ticks); + } + private int FileLookup(String FileName) + { + FileName = FileName.Substring(1); + if ("" == FileName) + return 0; /* root */ + int Count = FileCount(); + Boolean Valid = int.TryParse(FileName, out int Index); + if (!Valid || 0 >= Index || Index > Count) + return -1; /* not found */ + return Index; /* regular file named 1, 2, ..., Count */ + } + private static Byte[] FileContents(int Index) + { + if (0 == Index) + return EmptyByteArray; + return Encoding.UTF8.GetBytes(String.Format("{0}\n", Index)); + } + private static void FillFileInfo(int Index, out FileInfo FileInfo) + { + FileInfo = default(FileInfo); + FileInfo.FileAttributes = 0 == Index ? (UInt32)System.IO.FileAttributes.Directory : 0; + FileInfo.FileSize = (UInt64)FileContents(Index).Length; + FileInfo.AllocationSize = (FileInfo.FileSize + ALLOCATION_UNIT - 1) + / ALLOCATION_UNIT * ALLOCATION_UNIT; + FileInfo.CreationTime = + FileInfo.LastAccessTime = + FileInfo.LastWriteTime = + FileInfo.ChangeTime = (UInt64)DateTime.Now.ToFileTimeUtc(); + } + private void Tick(Object Context) + { + int Ticks = Interlocked.Increment(ref _Ticks); + int OldCount = CountFromTicks(Ticks - 1); + int NewCount = CountFromTicks(Ticks); + NotifyInfo[] NotifyInfo = new NotifyInfo[1]; + + if (OldCount < NewCount) + { + NotifyInfo[0].FileName = String.Format("\\{0}", NewCount); + NotifyInfo[0].Action = NotifyAction.Added; + NotifyInfo[0].Filter = NotifyFilter.ChangeFileName; + Console.Error.WriteLine("CREATE \\{0}", NewCount); + } + else if (OldCount > NewCount) + { + NotifyInfo[0].FileName = String.Format("\\{0}", OldCount); + NotifyInfo[0].Action = NotifyAction.Removed; + NotifyInfo[0].Filter = NotifyFilter.ChangeFileName; + Console.Error.WriteLine("REMOVE \\{0}", OldCount); + } + + if (OldCount != NewCount) + { + if (STATUS_SUCCESS == _Host.NotifyBegin(500)) + { + _Host.Notify(NotifyInfo); + _Host.NotifyEnd(); + } + } + } + + static Notifyfs() + { + RawSecurityDescriptor RootSecurityDescriptor = new RawSecurityDescriptor( + "O:BAG:BAD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;WD)"); + DefaultSecurity = new Byte[RootSecurityDescriptor.BinaryLength]; + RootSecurityDescriptor.GetBinaryForm(DefaultSecurity, 0); + } + + private const int ALLOCATION_UNIT = 4096; + private static readonly Byte[] EmptyByteArray = new Byte[0]; + private static readonly Byte[] DefaultSecurity; + private FileSystemHost _Host; + private Timer _Timer; + private int _Ticks; + } + + class NotifyfsService : Service + { + private class CommandLineUsageException : Exception + { + public CommandLineUsageException(String Message = null) : base(Message) + { + HasMessage = null != Message; + } + + public bool HasMessage; + } + + private const String PROGNAME = "notifyfs-dotnet"; + + public NotifyfsService() : base("NotifyfsService") + { + } + + protected override void OnStart(String[] Args) + { + try + { + String VolumePrefix = null; + String MountPoint = null; + FileSystemHost Host = null; + Notifyfs Notifyfs = 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 'm': + argtos(Args, ref I, ref MountPoint); + break; + case 'u': + argtos(Args, ref I, ref VolumePrefix); + break; + default: + throw new CommandLineUsageException(); + } + } + + if (Args.Length > I) + throw new CommandLineUsageException(); + + if (null == MountPoint) + throw new CommandLineUsageException(); + + FileSystemHost.SetDebugLogFile("-"); + + Host = new FileSystemHost(Notifyfs = new Notifyfs()); + Host.Prefix = VolumePrefix; + if (0 > Host.Mount(MountPoint)) + throw new IOException("cannot mount file system"); + MountPoint = Host.MountPoint(); + _Host = Host; + + Log(EVENTLOG_INFORMATION_TYPE, String.Format("{0}{1}{2} -m {3}", + PROGNAME, + null != VolumePrefix && 0 < VolumePrefix.Length ? " -u " : "", + null != VolumePrefix && 0 < VolumePrefix.Length ? VolumePrefix : "", + MountPoint)); + } + catch (CommandLineUsageException ex) + { + Log(EVENTLOG_ERROR_TYPE, String.Format( + "{0}" + + "usage: {1} OPTIONS\n" + + "\n" + + "options:\n" + + " -u \\Server\\Share [UNC prefix (single backslash)]\n" + + " -m MountPoint [X:|*|directory]\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 FileSystemHost _Host; + } + + class Program + { + static void Main(string[] args) + { + Environment.ExitCode = new NotifyfsService().Run(); + } + } +} diff --git a/tst/notifyfs-dotnet/notifyfs-dotnet.csproj b/tst/notifyfs-dotnet/notifyfs-dotnet.csproj new file mode 100644 index 00000000..c0adcb0a --- /dev/null +++ b/tst/notifyfs-dotnet/notifyfs-dotnet.csproj @@ -0,0 +1,77 @@ + + + + + Debug + AnyCPU + {DA7C383A-D10F-4FB0-BDCB-E7A7C5D068AA} + Exe + notifyfs + notifyfs-dotnet + v4.5.2 + 512 + true + true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + AnyCPU + true + full + false + $(SolutionDir)build\$(Configuration)\ + $(SolutionDir)build\$(ProjectName).build\ + $(BaseIntermediateOutputPath)$(Configuration)\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + $(SolutionDir)build\$(Configuration)\ + $(SolutionDir)build\$(ProjectName).build\ + $(BaseIntermediateOutputPath)$(Configuration)\ + TRACE + prompt + 4 + + + + + + + + $(MSBuildProgramFiles32)\WinFsp\bin\winfsp-msil.dll + + + + + False + Microsoft .NET Framework 4.5.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + \ No newline at end of file diff --git a/tst/notifyfs-dotnet/notifyfs-dotnet.sln b/tst/notifyfs-dotnet/notifyfs-dotnet.sln new file mode 100644 index 00000000..91975cd8 --- /dev/null +++ b/tst/notifyfs-dotnet/notifyfs-dotnet.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30717.126 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "notifyfs-dotnet", "notifyfs-dotnet.csproj", "{DA7C383A-D10F-4FB0-BDCB-E7A7C5D068AA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DA7C383A-D10F-4FB0-BDCB-E7A7C5D068AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA7C383A-D10F-4FB0-BDCB-E7A7C5D068AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA7C383A-D10F-4FB0-BDCB-E7A7C5D068AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA7C383A-D10F-4FB0-BDCB-E7A7C5D068AA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {18D87005-09CA-40CF-9B51-139B486AB8D0} + EndGlobalSection +EndGlobal