mirror of
https://github.com/winfsp/winfsp.git
synced 2025-07-03 01:12:58 -05:00
tst: notifyfs-dotnet: add .NET file system to demo file notification mechanism
This commit is contained in:
394
tst/notifyfs-dotnet/Program.cs
Normal file
394
tst/notifyfs-dotnet/Program.cs
Normal file
@ -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<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();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user