/** * @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. */ 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(); } } }