mirror of
				https://github.com/winfsp/winfsp.git
				synced 2025-10-31 12:08:41 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			395 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
		
			13 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.
 | |
|  */
 | |
| 
 | |
| 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<String> Enumerator = (IEnumerator<String>)Context;
 | |
| 
 | |
|             if (null == Enumerator)
 | |
|             {
 | |
|                 List<String> ChildrenFileNames = new List<String>();
 | |
|                 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();
 | |
|         }
 | |
|     }
 | |
| }
 |