mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 08:23:05 -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();
|
|
}
|
|
}
|
|
}
|