├── .editorconfig ├── .gitignore ├── CHANGELOG.md ├── Directory.Build.props ├── DokanNet.Tests ├── BufferPoolTests.cs ├── ContextTests.cs ├── DirectoryInfoTest.cs ├── DokanNet.Tests.csproj ├── DokanNet.Tests.ruleset ├── DokanOperationsFixture.cs ├── DriveInfoTests.cs ├── FileAccessUtils.cs ├── FileInfoTests.cs ├── FileInfoTestsUnsafe.cs ├── FileSettings.cs ├── FileSystemSecurityExtensions.cs ├── FormatProviderTests.cs ├── GlobalSuppressions.cs ├── LogExtensions.cs ├── Mounter.cs ├── OverlappedTests.Configuration.xml ├── OverlappedTests.cs ├── StringExtensions.cs └── TestCategories.cs ├── DokanNet.runsettings ├── DokanNet.sln ├── DokanNet.testsettings ├── DokanNet ├── BufferPool.cs ├── ByHandleFileInformation.cs ├── Dokan.cs ├── Dokan.snk ├── DokanException.cs ├── DokanFileInfo.cs ├── DokanHandle.cs ├── DokanHelper.cs ├── DokanInstance.cs ├── DokanInstanceBuilder.cs ├── DokanInstanceNotifyCompletion.cs ├── DokanNet.csproj ├── DokanOperationProxy.cs ├── DokanOptions.cs ├── DokanResult.cs ├── DokanStatus.cs ├── FileAccess.cs ├── FileInformation.cs ├── FileSystemFeatures.cs ├── FindFileInformation.cs ├── IDokanFileInfo.cs ├── IDokanOperations.cs ├── IDokanOperations2.cs ├── IDokanOperationsUnsafe.cs ├── Legacy │ └── DokanOperationsAdapter.cs ├── Logging │ ├── ConsoleLogger.cs │ ├── DebugViewLogger.cs │ ├── ILogger.cs │ ├── Logger.cs │ ├── LoggerExtensions.cs │ ├── NullLogger.cs │ └── TraceLogger.cs ├── MockDokanFileInfo.cs ├── Native │ ├── BY_HANDLE_FILE_INFORMATION.cs │ ├── DOKAN_OPERATIONS.cs │ ├── DOKAN_OPTIONS.cs │ ├── NativeMethods.cs │ ├── SECURITY_INFORMATION.cs │ ├── WIN32_FIND_DATA.cs │ └── WIN32_FIND_STREAM_DATA.cs ├── NtStatus.cs ├── NullFormatProvider.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.de.resx │ ├── Resources.fr.resx │ ├── Resources.resx │ └── Resources.sv.resx └── documentations │ ├── Doxyfile │ ├── mainpage.md │ └── resources │ ├── customdoxygen.css │ ├── doxy-boot.js │ ├── footer.html │ └── header.html ├── README.md ├── UpgradeLog.htm ├── appveyor.yml ├── dokan_logo.png ├── license.txt └── sample ├── DokanNetMirror ├── DokanNetMirror.csproj ├── Extensions.cs ├── Mirror.cs ├── NativeMethods.cs ├── Notify.cs ├── Program.cs └── UnsafeMirror.cs ├── DokanNetMirrorLegacy ├── DokanNetMirrorLegacy.csproj ├── Extensions.cs ├── Mirror.cs ├── NativeMethods.cs ├── Notify.cs ├── Program.cs └── UnsafeMirror.cs └── RegistryFS ├── Program.cs ├── Properties └── launchSettings.json ├── RFS.cs └── RegistryFS.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome:http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Don't use tabs for indentation. 7 | [*] 8 | indent_style = space 9 | # (Please don't specify an indent_size here; that has too many unintended consequences.) 10 | 11 | # Code files 12 | [*.{cs,csx,vb,vbx}] 13 | indent_size = 4 14 | 15 | # Xml project files 16 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 17 | indent_size = 2 18 | 19 | # Xml config files 20 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 21 | indent_size = 2 22 | 23 | # JSON files 24 | [*.json] 25 | indent_size = 2 26 | 27 | # Dotnet code style settings: 28 | [*.{cs,vb}] 29 | # Sort using and Import directives with System.* appearing first 30 | dotnet_sort_system_directives_first = true 31 | # Avoid "this." and "Me." if not necessary 32 | dotnet_style_qualification_for_field = false:suggestion 33 | dotnet_style_qualification_for_property = false:suggestion 34 | dotnet_style_qualification_for_method = false:suggestion 35 | dotnet_style_qualification_for_event = false:suggestion 36 | 37 | # Use language keywords instead of framework type names for type references 38 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 39 | dotnet_style_predefined_type_for_member_access = true:suggestion 40 | 41 | # Suggest more modern language features when available 42 | dotnet_style_object_initializer = true:suggestion 43 | dotnet_style_collection_initializer = true:suggestion 44 | dotnet_style_coalesce_expression = true:suggestion 45 | dotnet_style_null_propagation = true:suggestion 46 | dotnet_style_explicit_tuple_names = true:suggestion 47 | 48 | # CSharp code style settings: 49 | [*.cs] 50 | # Prefer "var" everywhere 51 | csharp_style_var_for_built_in_types = true:suggestion 52 | csharp_style_var_when_type_is_apparent = true:suggestion 53 | csharp_style_var_elsewhere = true:suggestion 54 | 55 | # Prefer method-like constructs to have a block body 56 | csharp_style_expression_bodied_methods = false:none 57 | csharp_style_expression_bodied_constructors = false:none 58 | csharp_style_expression_bodied_operators = false:none 59 | 60 | # Prefer property-like constructs to have an expression-body 61 | csharp_style_expression_bodied_properties = true:none 62 | csharp_style_expression_bodied_indexers = true:none 63 | csharp_style_expression_bodied_accessors = true:none 64 | 65 | # Suggest more modern language features when available 66 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 67 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 68 | csharp_style_inlined_variable_declaration = true:suggestion 69 | csharp_style_throw_expression = true:suggestion 70 | csharp_style_conditional_delegate_call = true:suggestion 71 | 72 | # Newline settings 73 | csharp_new_line_before_open_brace = all 74 | csharp_new_line_before_else = true 75 | csharp_new_line_before_catch = true 76 | csharp_new_line_before_finally = true 77 | csharp_new_line_before_members_in_object_initializers = true 78 | csharp_new_line_before_members_in_anonymous_types = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | *.nupkg 4 | bin 5 | obj 6 | packages 7 | TestResults 8 | /DokanNet/documentations/doc 9 | *.lock.json 10 | *.db 11 | *.opendb 12 | .vs 13 | *.DotSettings 14 | **/launchSettings.json -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | latest 5 | enable 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /DokanNet.Tests/BufferPoolTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DokanNet.Logging; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace DokanNet.Tests 6 | { 7 | /// 8 | /// Tests for . 9 | /// 10 | [TestClass] 11 | public sealed class BufferPoolTests 12 | { 13 | /// 14 | /// Rudimentary test for . 15 | /// 16 | [TestMethod, TestCategory(TestCategories.Success)] 17 | public void BufferPoolBasicTest() 18 | { 19 | BufferPool pool = new BufferPool(); 20 | ILogger logger = new TraceLogger(); 21 | 22 | // Verify buffer is pooled. 23 | const int MB = 1024 * 1024; 24 | byte[] buffer = pool.RentBuffer(MB, logger); 25 | pool.ReturnBuffer(buffer, logger); 26 | 27 | byte[] buffer2 = pool.RentBuffer(MB, logger); 28 | Assert.AreSame(buffer, buffer2, "Expected recycling of 1 MB buffer."); 29 | 30 | // Verify buffer that buffer not power of 2 is not pooled. 31 | buffer = pool.RentBuffer(MB - 1, logger); 32 | pool.ReturnBuffer(buffer, logger); 33 | 34 | buffer2 = pool.RentBuffer(MB - 1, logger); 35 | Assert.AreNotSame(buffer, buffer2, "Did not expect recycling of 1 MB - 1 byte buffer."); 36 | 37 | // Run through a bunch of random buffer sizes and make sure we always get a buffer of the right size. 38 | int seed = Environment.TickCount; 39 | Console.WriteLine($"Random seed: {seed}"); 40 | Random random = new Random(seed); 41 | 42 | for (int i = 0; i < 1000; i++) 43 | { 44 | int size = random.Next(0, 2 * MB); 45 | buffer = pool.RentBuffer(size, logger); 46 | Assert.AreEqual(size, buffer.Length, "Wrong buffer size."); 47 | pool.ReturnBuffer(buffer, logger); 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /DokanNet.Tests/DokanNet.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 18 | net48 19 | 20 | $(MSBuildProjectName) 21 | True 22 | disable 23 | 24 | false 25 | ..\DokanNet\Dokan.snk 26 | 27 | 28 | 29 | OverlappedTests.cs 30 | PreserveNewest 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | DokanNet.Tests.ruleset 47 | 48 | False 49 | 50 | -------------------------------------------------------------------------------- /DokanNet.Tests/DokanNet.Tests.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /DokanNet.Tests/DriveInfoTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using static DokanNet.Tests.FileSettings; 5 | 6 | namespace DokanNet.Tests 7 | { 8 | [TestClass] 9 | public sealed class DriveInfoTests 10 | { 11 | public TestContext TestContext { get; set; } 12 | 13 | [TestInitialize] 14 | public void Initialize() 15 | { 16 | DokanOperationsFixture.InitInstance(TestContext.TestName); 17 | } 18 | 19 | [TestCleanup] 20 | public void Cleanup() 21 | { 22 | DokanOperationsFixture.ClearInstance(out bool hasUnmatchedInvocations); 23 | Assert.IsFalse(hasUnmatchedInvocations, "Found Mock invocations without corresponding setups"); 24 | } 25 | 26 | [TestMethod, TestCategory(TestCategories.Success)] 27 | public void GetAvailableFreeSpace_CallsApiCorrectly() 28 | { 29 | var fixture = DokanOperationsFixture.Instance; 30 | 31 | #if LOGONLY 32 | fixture.PermitAny(); 33 | #else 34 | var availableFreeSpace = 1 << 10; 35 | fixture.ExpectGetDiskFreeSpace(freeBytesAvailable: availableFreeSpace); 36 | #endif 37 | 38 | var sut = new DriveInfo(DokanOperationsFixture.MOUNT_POINT); 39 | 40 | #if LOGONLY 41 | Assert.AreEqual(0, sut.AvailableFreeSpace, nameof(sut.AvailableFreeSpace)); 42 | #else 43 | Assert.AreEqual(availableFreeSpace, sut.AvailableFreeSpace, nameof(sut.AvailableFreeSpace)); 44 | 45 | fixture.Verify(); 46 | #endif 47 | } 48 | 49 | [TestMethod, TestCategory(TestCategories.Success)] 50 | public void GetDriveFormat_CallsApiCorrectly() 51 | { 52 | var fixture = DokanOperationsFixture.Instance; 53 | 54 | #if LOGONLY 55 | fixture.PermitAny(); 56 | #else 57 | fixture.ExpectOpenDirectory(DokanOperationsFixture.RootName, FileAccess.Synchronize, FileShare.ReadWrite); 58 | fixture.ExpectGetVolumeInformation(DokanOperationsFixture.VOLUME_LABEL, DokanOperationsFixture.FILESYSTEM_NAME, 256); 59 | #endif 60 | 61 | var sut = new DriveInfo(DokanOperationsFixture.MOUNT_POINT); 62 | 63 | #if LOGONLY 64 | Assert.IsNotNull(sut.DriveFormat, nameof(sut.DriveFormat)); 65 | Console.WriteLine(sut.DriveFormat); 66 | #else 67 | Assert.AreEqual(DokanOperationsFixture.FILESYSTEM_NAME, sut.DriveFormat, nameof(sut.DriveFormat)); 68 | 69 | fixture.Verify(); 70 | #endif 71 | } 72 | 73 | [TestMethod, TestCategory(TestCategories.Success)] 74 | public void GetDriveType_CallsApiCorrectly() 75 | { 76 | var sut = new DriveInfo(DokanOperationsFixture.MOUNT_POINT); 77 | 78 | #if NETWORK_DRIVE 79 | Assert.AreEqual(DriveType.Network, sut.DriveType, nameof(sut.DriveType)); 80 | #else 81 | Assert.AreEqual(DriveType.Removable, sut.DriveType, nameof(sut.DriveType)); 82 | #endif 83 | } 84 | 85 | [TestMethod, TestCategory(TestCategories.Success)] 86 | public void GetIsReady_CallsApiCorrectly() 87 | { 88 | var fixture = DokanOperationsFixture.Instance; 89 | 90 | var path = DokanOperationsFixture.RootName; 91 | #if LOGONLY 92 | fixture.PermitAny(); 93 | #else 94 | var anyDateTime = new DateTime(2000, 1, 1, 12, 0, 0); 95 | fixture.ExpectCreateFile(path, ReadAttributesAccess, ReadWriteShare, FileMode.Open); 96 | fixture.ExpectGetFileInformation(path, FileAttributes.Directory, creationTime: anyDateTime, lastWriteTime: anyDateTime, lastAccessTime: anyDateTime); 97 | #endif 98 | 99 | var sut = new DriveInfo(DokanOperationsFixture.MOUNT_POINT); 100 | 101 | #if LOGONLY 102 | Console.WriteLine($"sut.IsReady {sut.IsReady}"); 103 | #else 104 | Assert.IsTrue(sut.IsReady, nameof(sut.IsReady)); 105 | #endif 106 | } 107 | 108 | [TestMethod, TestCategory(TestCategories.Success)] 109 | public void GetName_CallsApiCorrectly() 110 | { 111 | var path = DokanOperationsFixture.RootName.AsDriveBasedPath(); 112 | 113 | var sut = new DriveInfo(DokanOperationsFixture.MOUNT_POINT); 114 | 115 | Assert.AreEqual(path, sut.Name, nameof(sut.Name)); 116 | } 117 | 118 | [TestMethod, TestCategory(TestCategories.Success)] 119 | public void GetRootDirectory_CallsApiCorrectly() 120 | { 121 | var path = DokanOperationsFixture.RootName.AsDriveBasedPath(); 122 | 123 | #if LOGONLY 124 | var fixture = DokanOperationsFixture.Instance; 125 | fixture.PermitAny(); 126 | #endif 127 | 128 | var sut = new DriveInfo(DokanOperationsFixture.MOUNT_POINT); 129 | 130 | #if LOGONLY 131 | Assert.IsNotNull(sut.RootDirectory, nameof(sut.RootDirectory)); 132 | Console.WriteLine(sut.RootDirectory); 133 | #else 134 | Assert.AreEqual(path, sut.RootDirectory.Name, nameof(sut.RootDirectory)); 135 | #endif 136 | } 137 | 138 | [TestMethod, TestCategory(TestCategories.Success)] 139 | public void GetTotalFreeSpace_CallsApiCorrectly() 140 | { 141 | var fixture = DokanOperationsFixture.Instance; 142 | 143 | #if LOGONLY 144 | fixture.PermitAny(); 145 | #else 146 | var totalFreeSpace = 1 << 14; 147 | fixture.ExpectGetDiskFreeSpace(totalNumberOfFreeBytes: totalFreeSpace); 148 | #endif 149 | 150 | var sut = new DriveInfo(DokanOperationsFixture.MOUNT_POINT); 151 | 152 | #if LOGONLY 153 | Assert.AreEqual(0, sut.TotalFreeSpace, nameof(sut.TotalFreeSpace)); 154 | #else 155 | Assert.AreEqual(totalFreeSpace, sut.TotalFreeSpace, nameof(sut.TotalFreeSpace)); 156 | 157 | fixture.Verify(); 158 | #endif 159 | } 160 | 161 | [TestMethod, TestCategory(TestCategories.Success)] 162 | public void GetTotalSize_CallsApiCorrectly() 163 | { 164 | var fixture = DokanOperationsFixture.Instance; 165 | 166 | #if LOGONLY 167 | fixture.PermitAny(); 168 | #else 169 | var totalSize = 1 << 20; 170 | fixture.ExpectGetDiskFreeSpace(totalNumberOfBytes: totalSize); 171 | #endif 172 | 173 | var sut = new DriveInfo(DokanOperationsFixture.MOUNT_POINT); 174 | 175 | #if LOGONLY 176 | Assert.AreEqual(0, sut.TotalSize, nameof(sut.TotalSize)); 177 | #else 178 | Assert.AreEqual(totalSize, sut.TotalSize, nameof(sut.TotalSize)); 179 | 180 | fixture.Verify(); 181 | #endif 182 | } 183 | 184 | [TestMethod, TestCategory(TestCategories.Success)] 185 | public void GetVolumeLabel_CallsApiCorrectly() 186 | { 187 | var fixture = DokanOperationsFixture.Instance; 188 | 189 | #if LOGONLY 190 | fixture.PermitAny(); 191 | #else 192 | fixture.ExpectOpenDirectory(DokanOperationsFixture.RootName, FileAccess.Synchronize, FileShare.ReadWrite); 193 | fixture.ExpectGetVolumeInformation(DokanOperationsFixture.VOLUME_LABEL, DokanOperationsFixture.FILESYSTEM_NAME, 256); 194 | #endif 195 | 196 | var sut = new DriveInfo(DokanOperationsFixture.MOUNT_POINT); 197 | 198 | #if LOGONLY 199 | Assert.IsNotNull(sut.VolumeLabel, nameof(sut.VolumeLabel)); 200 | Console.WriteLine(sut.VolumeLabel); 201 | #else 202 | Assert.AreEqual(DokanOperationsFixture.VOLUME_LABEL, sut.VolumeLabel, nameof(sut.VolumeLabel)); 203 | 204 | fixture.Verify(); 205 | #endif 206 | } 207 | } 208 | } -------------------------------------------------------------------------------- /DokanNet.Tests/FileAccessUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace DokanNet.Tests 5 | { 6 | static class FileAccessUtils 7 | { 8 | private const FileAccess FILE_GENERIC_READ = 9 | FileAccess.ReadAttributes | 10 | FileAccess.ReadData | 11 | FileAccess.ReadExtendedAttributes | 12 | FileAccess.ReadPermissions | 13 | FileAccess.Synchronize; 14 | 15 | private const FileAccess FILE_GENERIC_WRITE = 16 | FileAccess.AppendData | 17 | FileAccess.WriteAttributes | 18 | FileAccess.WriteData | 19 | FileAccess.WriteExtendedAttributes | 20 | FileAccess.ReadPermissions | 21 | FileAccess.Synchronize; 22 | 23 | private const FileAccess FILE_GENERIC_EXECUTE = 24 | FileAccess.Execute | 25 | FileAccess.ReadAttributes | 26 | FileAccess.ReadPermissions | 27 | FileAccess.Synchronize; 28 | 29 | private static readonly FileAccess FILE_ALL_ACCESS = (FileAccess)Enum.GetValues(typeof(FileAccess)).Cast().Sum(); 30 | 31 | public static FileAccess MapSpecificToGenericAccess(FileAccess desiredAccess) 32 | { 33 | var outDesiredAccess = desiredAccess; 34 | 35 | var genericRead = false; 36 | var genericWrite = false; 37 | var genericExecute = false; 38 | var genericAll = false; 39 | if ((outDesiredAccess & FILE_GENERIC_READ) == FILE_GENERIC_READ) 40 | { 41 | outDesiredAccess |= FileAccess.GenericRead; 42 | genericRead = true; 43 | } 44 | 45 | if ((outDesiredAccess & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE) 46 | { 47 | outDesiredAccess |= FileAccess.GenericWrite; 48 | genericWrite = true; 49 | } 50 | 51 | if ((outDesiredAccess & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE) 52 | { 53 | outDesiredAccess |= FileAccess.GenericExecute; 54 | genericExecute = true; 55 | } 56 | 57 | if ((outDesiredAccess & FILE_ALL_ACCESS) == FILE_ALL_ACCESS) 58 | { 59 | outDesiredAccess |= FileAccess.GenericAll; 60 | genericAll = true; 61 | } 62 | 63 | if (genericRead) 64 | { 65 | outDesiredAccess &= ~FILE_GENERIC_READ; 66 | } 67 | 68 | if (genericWrite) 69 | { 70 | outDesiredAccess &= ~FILE_GENERIC_WRITE; 71 | } 72 | 73 | if (genericExecute) 74 | { 75 | outDesiredAccess &= ~FILE_GENERIC_EXECUTE; 76 | } 77 | 78 | if (genericAll) 79 | { 80 | outDesiredAccess &= ~FILE_ALL_ACCESS; 81 | } 82 | 83 | return outDesiredAccess; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /DokanNet.Tests/FileInfoTestsUnsafe.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace DokanNet.Tests 4 | { 5 | /// 6 | /// Tests for . This is leveraging the same set of tests as 7 | /// by deriving from that class, but by calling 8 | /// DokanOperationsFixture.InitInstance(unsafeOperations: true) from setup to send all 9 | /// Read/WriteFile calls through the Read/WriteFile(IntPtr buffer, uint bufferLength) overloads instead 10 | /// of the Read/WriteFile(byte[] buffer) overloads. 11 | /// 12 | [TestClass] 13 | public sealed class FileInfoTestsUnsafe : FileInfoTests 14 | { 15 | [ClassInitialize] 16 | public static new void ClassInitialize(TestContext context) 17 | { 18 | // Just invoke the base class init. 19 | FileInfoTests.ClassInitialize(context); 20 | } 21 | 22 | [ClassCleanup] 23 | public static new void ClassCleanup() 24 | { 25 | // Just invoke the base class cleanup. 26 | FileInfoTests.ClassCleanup(); 27 | } 28 | 29 | [TestInitialize] 30 | public override void Initialize() 31 | { 32 | // Clear the buffer pool (so we can validate in Cleanup()) and init test fixture. 33 | BufferPool.Default.Clear(); 34 | DokanOperationsFixture.InitInstance(TestContext.TestName, unsafeOperations: true); 35 | } 36 | 37 | [TestCleanup] 38 | public override void Cleanup() 39 | { 40 | // Verify no buffers were pooled and then call base class Cleanup(). 41 | Assert.AreEqual(0, BufferPool.Default.ServedBytes, "Expected zero buffer pooling activity when using IDokanOperationsUnsafe."); 42 | BufferPool.Default.Clear(); 43 | base.Cleanup(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /DokanNet.Tests/FileSettings.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace DokanNet.Tests 4 | { 5 | internal static class FileSettings 6 | { 7 | public const FileAccess ReadAttributesAccess = FileAccess.ReadAttributes; 8 | 9 | public const FileAccess ReadPermissionsAccess = FileAccess.ReadPermissions; 10 | 11 | public const FileAccess ReadAttributesPermissionsAccess = ReadAttributesAccess | ReadPermissionsAccess; 12 | 13 | public const FileAccess ChangePermissionsAccess = FileAccess.ReadAttributes | FileAccess.ReadPermissions | FileAccess.ChangePermissions; 14 | 15 | public const FileAccess ReadAccess = FileAccess.ReadData | FileAccess.ReadExtendedAttributes | FileAccess.ReadAttributes | FileAccess.ReadPermissions | FileAccess.Synchronize; 16 | 17 | public const FileAccess WriteAccess = 18 | FileAccess.WriteData | FileAccess.AppendData | FileAccess.WriteExtendedAttributes | 19 | FileAccess.ReadAttributes | FileAccess.WriteAttributes | FileAccess.ReadPermissions | FileAccess.Synchronize; 20 | 21 | public const FileAccess ReadWriteAccess = ReadAccess | WriteAccess; 22 | 23 | public const FileAccess SetOwnershipAccess = ReadAccess | WriteAccess | FileAccess.Delete | FileAccess.ChangePermissions | FileAccess.SetOwnership; 24 | 25 | public const FileAccess DeleteAccess = FileAccess.ReadAttributes | FileAccess.Delete; 26 | 27 | public const FileAccess CopyToAccess = ReadAccess | WriteAccess | FileAccess.Delete | FileAccess.ChangePermissions; 28 | 29 | public const FileAccess MoveFromAccess = FileAccess.ReadAttributes | FileAccess.Delete | FileAccess.Synchronize; 30 | 31 | public const FileAccess ReplaceAccess = FileAccess.WriteData | FileAccess.ReadExtendedAttributes | FileAccess.ReadAttributes | FileAccess.Delete | FileAccess.ReadPermissions | FileAccess.Synchronize; 32 | 33 | public const FileAccess OpenDirectoryAccess = FileAccess.Synchronize; 34 | 35 | public const FileAccess ReadDirectoryAccess = FileAccess.ReadData | FileAccess.Synchronize; 36 | 37 | public const FileAccess WriteDirectoryAccess = FileAccess.WriteData | FileAccess.Synchronize; 38 | 39 | public const FileAccess AppendToDirectoryAccess = FileAccess.AppendData | FileAccess.Synchronize; 40 | 41 | public const FileAccess DeleteFromDirectoryAccess = FileAccess.Delete | FileAccess.ReadAttributes | FileAccess.Synchronize; 42 | 43 | public const FileShare ReadOnlyShare = FileShare.Read; 44 | 45 | public const FileShare ReadShare = FileShare.Read | FileShare.Delete; 46 | 47 | public const FileShare ReadWriteShare = FileShare.ReadWrite | FileShare.Delete; 48 | 49 | public const FileShare WriteShare = FileShare.None; 50 | 51 | public const FileShare OpenDirectoryShare = FileShare.None; 52 | 53 | public const FileOptions ReadFileOptions = FileOptions.None; 54 | 55 | public const FileOptions WriteFileOptions = FileOptions.None; 56 | 57 | public const FileOptions OpenReparsePointOptions = (FileOptions)0x00200000; 58 | 59 | public const FileOptions OpenNoBufferingOptions = (FileOptions)0x20000000; 60 | } 61 | } -------------------------------------------------------------------------------- /DokanNet.Tests/FileSystemSecurityExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Security.AccessControl; 2 | 3 | namespace DokanNet.Tests 4 | { 5 | internal static class FileSystemSecurityExtensions 6 | { 7 | public static string AsString(this FileSystemSecurity security) 8 | => security.GetSecurityDescriptorSddlForm(AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); 9 | } 10 | } -------------------------------------------------------------------------------- /DokanNet.Tests/FormatProviderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace DokanNet.Tests 5 | { 6 | [TestClass] 7 | public sealed class FormatProviderTests 8 | { 9 | [TestMethod, TestCategory(TestCategories.Success)] 10 | public void NullValuesShouldBeVisible() 11 | { 12 | DateTime? obj = null; 13 | Assert.AreEqual(FormatProviders.NullStringRepresentation, string.Format(FormatProviders.DefaultFormatProvider, "{0}", obj)); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /DokanNet.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dokan-dev/dokan-dotnet/7271b35414a31b2607c4abe59db9782bc2d6a572/DokanNet.Tests/GlobalSuppressions.cs -------------------------------------------------------------------------------- /DokanNet.Tests/LogExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DokanNet.Tests 2 | { 3 | internal static class LogExtensions 4 | { 5 | public static string Log(this IDokanFileInfo info) 6 | => $"{nameof(DokanFileInfo)} {{{info.Context ?? ""}, {(info.DeletePending ? nameof(info.DeletePending) : "")}, {(info.IsDirectory ? nameof(info.IsDirectory) : "")}, {(info.NoCache ? nameof(info.NoCache) : "")}, {(info.PagingIo ? nameof(info.PagingIo) : "")}, {info.ProcessId}, {(info.SynchronousIo ? nameof(info.SynchronousIo) : "")}, {(info.WriteToEndOfFile ? nameof(info.WriteToEndOfFile) : "")}}}"; 7 | 8 | public static string Log(this FileInformation fileInfo) 9 | => $"{nameof(FileInformation)} {{{fileInfo.FileName}, [{fileInfo.Attributes}], {fileInfo.CreationTime?.ToString() ?? ""}, {fileInfo.LastWriteTime?.ToString() ?? ""}, {fileInfo.LastAccessTime?.ToString() ?? ""}, {fileInfo.Length}}}"; 10 | } 11 | } -------------------------------------------------------------------------------- /DokanNet.Tests/Mounter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace DokanNet.Tests 6 | { 7 | [TestClass] 8 | public static class Mounter 9 | { 10 | private static Logging.NullLogger NullLogger = new Logging.NullLogger(); 11 | private static Dokan Dokan; 12 | private static DokanInstance safeMount; 13 | private static DokanInstance unsafeMount; 14 | 15 | [AssemblyInitialize] 16 | public static void AssemblyInitialize(TestContext context) 17 | { 18 | var dokanOptions = DokanOptions.DebugMode | DokanOptions.MountManager | DokanOptions.CurrentSession; 19 | #if NETWORK_DRIVE 20 | dokanOptions |= DokanOptions.NetworkDrive; 21 | #else 22 | dokanOptions |= DokanOptions.RemovableDrive; 23 | #endif 24 | #if USER_MODE_LOCK 25 | dokanOptions |= DokanOptions.UserModeLock; 26 | #endif 27 | 28 | Dokan = new Dokan(NullLogger); 29 | var safeDokanBuilder = new DokanInstanceBuilder(Dokan) 30 | .ConfigureOptions(options => 31 | { 32 | options.Options = dokanOptions; 33 | options.MountPoint = DokanOperationsFixture.NormalMountPoint; 34 | }); 35 | 36 | safeMount = safeDokanBuilder.Build(DokanOperationsFixture.Operations); 37 | 38 | var unsafeDokanBuilder = new DokanInstanceBuilder(Dokan) 39 | .ConfigureOptions(options => 40 | { 41 | options.Options = dokanOptions; 42 | options.MountPoint = DokanOperationsFixture.UnsafeMountPoint; 43 | }); 44 | unsafeMount = unsafeDokanBuilder.Build(DokanOperationsFixture.UnsafeOperations); 45 | var drive = new DriveInfo(DokanOperationsFixture.NormalMountPoint); 46 | var drive2 = new DriveInfo(DokanOperationsFixture.UnsafeMountPoint); 47 | while (!drive.IsReady || !drive2.IsReady) 48 | { 49 | Thread.Sleep(50); 50 | } 51 | 52 | while (DokanOperationsFixture.HasPendingFiles) 53 | { 54 | Thread.Sleep(50); 55 | } 56 | } 57 | 58 | [AssemblyCleanup] 59 | public static void AssemblyCleanup() 60 | { 61 | safeMount.Dispose(); 62 | unsafeMount.Dispose(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /DokanNet.Tests/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DokanNet.Tests 2 | { 3 | internal static class StringExtensions 4 | { 5 | public static string AsRootedPath(this string path) => DokanOperationsFixture.RootedPath(path); 6 | 7 | public static string AsDriveBasedPath(this string path) => DokanOperationsFixture.DriveBasedPath(path); 8 | } 9 | } -------------------------------------------------------------------------------- /DokanNet.Tests/TestCategories.cs: -------------------------------------------------------------------------------- 1 | namespace DokanNet.Tests 2 | { 3 | internal static class TestCategories 4 | { 5 | public const string Success = nameof(Success); 6 | 7 | public const string Failure = nameof(Failure); 8 | 9 | public const string Timing = nameof(Timing); 10 | 11 | public const string Manual = nameof(Manual); 12 | 13 | public const string NoPatternSearch = nameof(NoPatternSearch); 14 | } 15 | } -------------------------------------------------------------------------------- /DokanNet.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | x64 6 | -------------------------------------------------------------------------------- /DokanNet.testsettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | These are default test settings for a local test run. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /DokanNet/ByHandleFileInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace DokanNet; 7 | 8 | /// 9 | /// Used to provide file information to %Dokan during operations by 10 | /// - 11 | /// - 12 | /// - 13 | /// - . 14 | /// 15 | [StructLayout(LayoutKind.Auto)] 16 | [DebuggerDisplay("{Length}, {CreationTime}, {LastWriteTime}, {LastAccessTime}, {Attributes}")] 17 | public struct ByHandleFileInformation 18 | { 19 | /// 20 | /// Initializes with set to 1. 21 | /// 22 | public ByHandleFileInformation() 23 | { 24 | this = default; 25 | NumberOfLinks = 1; 26 | } 27 | 28 | /// 29 | /// Gets or sets the for the file or directory. 30 | /// 31 | public FileAttributes Attributes { get; set; } 32 | 33 | /// 34 | /// Gets or sets the creation time of the file or directory. 35 | /// If equal to null, the value will not be set or the file has no creation time. 36 | /// 37 | public DateTime? CreationTime { get; set; } 38 | 39 | /// 40 | /// Gets or sets the last access time of the file or directory. 41 | /// If equal to null, the value will not be set or the file has no last access time. 42 | /// 43 | public DateTime? LastAccessTime { get; set; } 44 | 45 | /// 46 | /// Gets or sets the last write time of the file or directory. 47 | /// If equal to null, the value will not be set or the file has no last write time. 48 | /// 49 | public DateTime? LastWriteTime { get; set; } 50 | 51 | /// 52 | /// Gets or sets the length of the file. 53 | /// 54 | public long Length { get; set; } 55 | 56 | /// 57 | /// Number of links to the same file. 58 | /// 59 | public int NumberOfLinks { get; set; } 60 | 61 | /// 62 | /// Index number of file in the file system. 63 | /// 64 | public long FileIndex { get; set; } 65 | } 66 | -------------------------------------------------------------------------------- /DokanNet/Dokan.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dokan-dev/dokan-dotnet/7271b35414a31b2607c4abe59db9782bc2d6a572/DokanNet/Dokan.snk -------------------------------------------------------------------------------- /DokanNet/DokanException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DokanNet.Properties; 3 | 4 | namespace DokanNet 5 | { 6 | /// 7 | /// The dokan exception. 8 | /// 9 | [Serializable] 10 | public class DokanException : Exception 11 | { 12 | /// 13 | /// Initializes a new instance of the class with a . 14 | /// 15 | /// 16 | /// The error status also written to . 17 | /// 18 | internal DokanException(DokanStatus status) 19 | : this(status, GetStatusErrorMessage(status)) { } 20 | 21 | /// 22 | /// Initializes a new instance of the class with a . 23 | /// 24 | /// 25 | /// The error status also written to . 26 | /// 27 | /// 28 | /// The error message. 29 | /// 30 | internal DokanException(DokanStatus status, string message) 31 | : base(message) 32 | { 33 | ErrorStatus = status; 34 | HResult = (int)status; 35 | } 36 | 37 | private static string GetStatusErrorMessage(DokanStatus status) 38 | { 39 | switch (status) 40 | { 41 | case DokanStatus.Error: 42 | return Resources.ErrorDokan; 43 | case DokanStatus.DriveLetterError: 44 | return Resources.ErrorBadDriveLetter; 45 | case DokanStatus.DriverInstallError: 46 | return Resources.ErrorDriverInstall; 47 | case DokanStatus.MountError: 48 | return Resources.ErrorAssignDriveLetter; 49 | case DokanStatus.StartError: 50 | return Resources.ErrorStart; 51 | case DokanStatus.MountPointError: 52 | return Resources.ErrorMountPointInvalid; 53 | case DokanStatus.VersionError: 54 | return Resources.ErrorVersion; 55 | default: 56 | return Resources.ErrorUnknown; 57 | } 58 | } 59 | 60 | /// 61 | /// Dokan error status . 62 | /// 63 | public DokanStatus ErrorStatus { get; private set; } 64 | } 65 | } -------------------------------------------------------------------------------- /DokanNet/DokanFileInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Runtime.Versioning; 3 | using System.Security.Principal; 4 | using DokanNet.Native; 5 | using Microsoft.Win32.SafeHandles; 6 | using static DokanNet.FormatProviders; 7 | 8 | namespace DokanNet; 9 | 10 | /// 11 | /// %Dokan file information on the current operation. 12 | /// 13 | /// 14 | /// This class cannot be instantiated in C#, it is created by the kernel %Dokan driver. 15 | /// This is the same structure as _DOKAN_FILE_INFO (dokan.h) in the C version of Dokan. 16 | /// 17 | #if NET5_0_OR_GREATER 18 | [SupportedOSPlatform("windows")] 19 | #endif 20 | [StructLayout(LayoutKind.Sequential, Pack = 4)] 21 | public struct DokanFileInfo 22 | { 23 | private long context; 24 | 25 | /// 26 | /// Used internally, never modify. 27 | /// 28 | private readonly ulong dokanContext; 29 | 30 | /// 31 | /// A pointer to the which was passed to . 32 | /// 33 | private readonly nint dokanOptions; 34 | 35 | /// 36 | /// Reserved. Used internally by Dokan library. Never modify. 37 | /// If the processing for the event requires extra data to be associated with it 38 | /// then a pointer to that data can be placed here 39 | /// 40 | private readonly nint processingContext; 41 | 42 | /// 43 | /// Process id for the thread that originally requested a given I/O 44 | /// operation. 45 | /// 46 | public readonly int ProcessId { get; } 47 | 48 | private byte isDirectory; 49 | private byte deletePending; 50 | private readonly byte pagingIo; 51 | private readonly byte synchronousIo; 52 | private readonly byte noCache; 53 | private readonly byte writeToEndOfFile; 54 | 55 | /// 56 | /// Gets or sets a value indicating whether it requesting a directory 57 | /// file. Must be set in if 58 | /// the file appear to be a folder. 59 | /// 60 | public bool IsDirectory { readonly get => isDirectory != 0; set => isDirectory = value ? (byte)1 : (byte)0; } 61 | 62 | /// 63 | /// Gets or sets a value indicating whether the file has to be deleted 64 | /// during the event. 65 | /// 66 | public bool DeletePending { readonly get => deletePending != 0; set => deletePending = value ? (byte)1 : (byte)0; } 67 | 68 | /// 69 | /// Read or write is paging IO. 70 | /// 71 | public readonly bool PagingIo => pagingIo != 0; 72 | 73 | /// 74 | /// Read or write is synchronous IO. 75 | /// 76 | public readonly bool SynchronousIo => synchronousIo != 0; 77 | 78 | /// 79 | /// Read or write directly from data source without cache. 80 | /// 81 | public readonly bool NoCache => noCache != 0; 82 | 83 | /// 84 | /// If true, write to the current end of file instead 85 | /// of using the Offset parameter. 86 | /// 87 | public readonly bool WriteToEndOfFile => writeToEndOfFile != 0; 88 | 89 | /// 90 | /// Gets or sets context that can be used to carry information between operation. 91 | /// The Context can carry whatever type like , struct, int, 92 | /// or internal reference that will help the implementation understand the request context of the event. 93 | /// 94 | public object? Context 95 | { 96 | readonly get 97 | { 98 | if (context != 0) 99 | { 100 | return ((GCHandle)(nint)context).Target; 101 | } 102 | 103 | return null; 104 | } 105 | 106 | set 107 | { 108 | if (context != 0) 109 | { 110 | ((GCHandle)(nint)context).Free(); 111 | context = 0; 112 | } 113 | 114 | if (value is not null) 115 | { 116 | context = (nint)GCHandle.Alloc(value); 117 | } 118 | } 119 | } 120 | 121 | /// 122 | /// This method needs to be called in . 123 | /// 124 | /// An with the access token. 125 | public readonly WindowsIdentity GetRequestor() 126 | { 127 | using var sfh = GetRequestorToken(); 128 | 129 | return new(sfh.DangerousGetHandle()); 130 | } 131 | 132 | /// 133 | /// This method needs to be called in . 134 | /// 135 | /// A with the access token. 136 | public readonly SafeAccessTokenHandle GetRequestorToken() => NativeMethods.DokanOpenRequestorToken(this); 137 | 138 | /// 139 | /// Extends the time out of the current IO operation in driver. 140 | /// 141 | /// Number of milliseconds to extend with. 142 | /// If the operation was successful. 143 | public readonly bool TryResetTimeout(int milliseconds) => NativeMethods.DokanResetTimeout((uint)milliseconds, this); 144 | 145 | /// Returns a string that represents the current object. 146 | /// A string that represents the current object. 147 | public override readonly string ToString() => DokanFormat( 148 | $"{{{nameof(Context)}=0x{context:X}:'{Context}', {nameof(DeletePending)}={DeletePending}, {nameof(IsDirectory)}={IsDirectory}, {nameof(NoCache)}={NoCache}, {nameof(PagingIo)}={PagingIo}, {nameof(ProcessId)}={ProcessId}, {nameof(SynchronousIo)}={SynchronousIo}, {nameof(WriteToEndOfFile)}={WriteToEndOfFile}}}")!; 149 | } 150 | -------------------------------------------------------------------------------- /DokanNet/DokanHandle.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Runtime.Versioning; 3 | using DokanNet.Native; 4 | using Microsoft.Win32.SafeHandles; 5 | 6 | namespace DokanNet 7 | { 8 | /// 9 | /// This class wraps a native DOKAN_HANDLE. 10 | /// 11 | /// Since this class derives form SafeHandle, it is automatically marshalled as 12 | /// the native handle it represents to and from native code in for example P/Invoke 13 | /// calls. It also uses reference counting and guaranteed to stay alive during such calls. 14 | /// 15 | /// 16 | #if NET5_0_OR_GREATER 17 | [SupportedOSPlatform("windows")] 18 | #endif 19 | internal class DokanHandle : SafeHandleZeroOrMinusOneIsInvalid 20 | { 21 | /// 22 | /// Initializes a new empty instance, specifying whether the handle is to be reliably released. 23 | /// Used internally by native marshaller and not intended to be used directly from user code. 24 | /// 25 | /// true to reliably release the handle during the finalization phase; false to prevent 26 | /// reliable release (not recommended). 27 | public DokanHandle(bool ownsHandle) : base(ownsHandle) 28 | { 29 | } 30 | 31 | /// 32 | /// Initializes an empty instance. Used internally by native marshaller and 33 | /// not intended to be used directly from user code. 34 | /// 35 | public DokanHandle() : base(ownsHandle: true) 36 | { 37 | } 38 | 39 | /// 40 | /// Releases the native DOKAN_HANDLE wrapped by this instance. 41 | /// 42 | /// Always returns true 43 | protected override bool ReleaseHandle() 44 | { 45 | NativeMethods.DokanCloseHandle(handle); 46 | return true; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /DokanNet/DokanInstanceBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Runtime.Versioning; 4 | using DokanNet.Legacy; 5 | using DokanNet.Logging; 6 | using DokanNet.Native; 7 | 8 | namespace DokanNet; 9 | 10 | /// 11 | /// Dokan FileSystem build helper. Allows to create one or multiple based on a given configuration. 12 | /// 13 | public class DokanInstanceBuilder 14 | { 15 | /// 16 | /// Delegate used by to customize th internal . 17 | /// 18 | public delegate void OptionsConfigurationDelegate(DOKAN_OPTIONS options); 19 | 20 | /// 21 | /// The Dokan version that DokanNet is compatible with. 22 | /// 23 | /// 24 | public const ushort DOKAN_VERSION = 200; 25 | 26 | private readonly DOKAN_OPTIONS _options; 27 | 28 | private readonly Dokan _dokan; 29 | 30 | private ILogger _logger; 31 | 32 | /// 33 | /// Constructure an object with a and default that will use the given . 34 | /// 35 | public DokanInstanceBuilder(Dokan dokan) 36 | { 37 | _logger = new NullLogger(); 38 | _options = new DOKAN_OPTIONS 39 | { 40 | Version = DOKAN_VERSION, 41 | MountPoint = "", 42 | UNCName = string.Empty, 43 | SingleThread = false, 44 | Options = DokanOptions.FixedDrive, 45 | TimeOut = TimeSpan.FromSeconds(20), 46 | AllocationUnitSize = 512, 47 | SectorSize = 512, 48 | VolumeSecurityDescriptorLength = 0, 49 | 50 | }; 51 | _dokan = dokan; 52 | } 53 | 54 | /// 55 | /// Allows to set a custom like , to be used 56 | /// for the instance created by . 57 | /// 58 | public DokanInstanceBuilder ConfigureLogger(Func IlogegrFactory) 59 | { 60 | _logger = IlogegrFactory(); 61 | return this; 62 | } 63 | 64 | /// 65 | /// Allows to personalize the use for . 66 | /// 67 | public DokanInstanceBuilder ConfigureOptions(OptionsConfigurationDelegate optionsConfigurationDelegate) 68 | { 69 | optionsConfigurationDelegate(_options); 70 | return this; 71 | } 72 | 73 | /// 74 | /// Verify that the provided configuration is valid. 75 | /// Note: Has no effect for now. 76 | /// 77 | public DokanInstanceBuilder Validate() 78 | { 79 | // throw on errors 80 | return this; 81 | } 82 | 83 | /// 84 | /// Create a based on the previously provided information 85 | /// through and . 86 | /// 87 | #if NET5_0_OR_GREATER 88 | [SupportedOSPlatform("windows")] 89 | #endif 90 | public DokanInstance Build(IDokanOperations2 operations) 91 | { 92 | return new DokanInstance(_logger, _options, _dokan, operations); 93 | } 94 | 95 | /// 96 | /// Create a based on the previously provided information 97 | /// through and . 98 | /// 99 | #if NET5_0_OR_GREATER 100 | [SupportedOSPlatform("windows")] 101 | #endif 102 | public DokanInstance Build(IDokanOperations operations) 103 | { 104 | return new DokanInstance(_logger, _options, _dokan, new DokanOperationsAdapter(operations, _logger)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /DokanNet/DokanInstanceNotifyCompletion.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | using System; 6 | using DokanNet.Native; 7 | using System.Runtime.Versioning; 8 | 9 | namespace DokanNet; 10 | 11 | #if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP 12 | 13 | /// 14 | /// Support for async/await operation on DokanInstance objects 15 | /// 16 | #if NET5_0_OR_GREATER 17 | [SupportedOSPlatform("windows")] 18 | #endif 19 | internal sealed class DokanInstanceNotifyCompletion : ICriticalNotifyCompletion 20 | { 21 | public DokanInstanceNotifyCompletion(DokanInstance dokanInstance, uint milliSeconds) 22 | { 23 | DokanInstance = dokanInstance; 24 | MilliSeconds = milliSeconds; 25 | } 26 | 27 | public DokanInstance DokanInstance { get; } 28 | public uint MilliSeconds { get; } 29 | public bool IsCompleted => !DokanInstance.IsFileSystemRunning(); 30 | private nint waitHandle; 31 | private bool timedOut; 32 | private Action? continuation; 33 | 34 | public DokanInstanceNotifyCompletion GetAwaiter() => this; 35 | 36 | public void OnCompleted(Action continuation) => throw new NotSupportedException(); 37 | 38 | public void UnsafeOnCompleted(Action continuation) 39 | { 40 | this.continuation = continuation; 41 | 42 | if (!NativeMethods.DokanRegisterWaitForFileSystemClosed(DokanInstance.DokanHandle, 43 | out waitHandle, 44 | Callback, 45 | (nint)GCHandle.Alloc(this), 46 | MilliSeconds)) 47 | { 48 | throw new Win32Exception(); 49 | } 50 | } 51 | 52 | private static void Callback(nint state, bool timedOut) 53 | { 54 | var handle = GCHandle.FromIntPtr(state); 55 | var target = (DokanInstanceNotifyCompletion)handle.Target!; 56 | 57 | handle.Free(); 58 | 59 | while (target.waitHandle == 0) 60 | { 61 | Thread.Sleep(20); 62 | } 63 | 64 | NativeMethods.DokanUnregisterWaitForFileSystemClosed(target.waitHandle, 65 | waitForCallbacks: false); 66 | 67 | target.waitHandle = 0; 68 | 69 | target.timedOut = timedOut; 70 | 71 | target.continuation!(); 72 | } 73 | 74 | /// 75 | /// Gets a value indicating whether DokanInstance was closed or if await timed out 76 | /// 77 | /// True if DokanInstance was closed or false if await timed out 78 | public bool GetResult() 79 | { 80 | if (timedOut) 81 | { 82 | return false; 83 | } 84 | 85 | if (!IsCompleted) 86 | { 87 | throw new InvalidOperationException($"Invalid state for {nameof(GetResult)}"); 88 | } 89 | 90 | return true; 91 | } 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /DokanNet/DokanNet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard2.1;net48;net46;net8.0;net9.0 5 | DokanNet 6 | DokanNet 7 | A user mode file system for windows. 8 | 9 | This is a .NET wrapper over Dokan, and allows you to create your own file systems in Windows. 10 | Copyright (C) 2025 11 | 2.3.0.1 12 | 2.3.0.1 13 | 2.3.0.1 14 | True 15 | true 16 | README.md 17 | Dokan-dev 18 | AdrienJ, MaximeC, Hiroki Asakawa 19 | https://dokan-dev.github.io/ 20 | MIT 21 | dokan_logo.png 22 | https://github.com/dokan-dev/dokan-dotnet 23 | git 24 | dokan file files disk directory storage filesystem io filestore FAT NTFS FUSE 25 | Dokan.snk 26 | false 27 | True 28 | Latest 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /DokanNet/DokanOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DokanNet.Native; 3 | 4 | namespace DokanNet 5 | { 6 | /// 7 | /// Dokan mount options used to describe dokan device behavior. 8 | /// 9 | /// \if PRIVATE 10 | /// 11 | /// \endif 12 | [Flags] 13 | public enum DokanOptions : uint 14 | { 15 | /// Fixed Drive. 16 | FixedDrive = 0, 17 | 18 | /// Enable output debug message. 19 | DebugMode = 1, 20 | 21 | /// Enable output debug message to stderr. 22 | StderrOutput = 2, 23 | 24 | /// Use alternate stream. 25 | AltStream = 4, 26 | 27 | /// Enable mount drive as write-protected. 28 | WriteProtection = 8, 29 | 30 | /// Use network drive - Dokan network provider need to be installed. 31 | NetworkDrive = 16, 32 | 33 | /// Use removable drive. 34 | RemovableDrive = 32, 35 | 36 | /// Use mount manager. 37 | MountManager = 64, 38 | 39 | /// Mount the drive on current session only. 40 | CurrentSession = 128, 41 | 42 | /// Enable Lockfile/Unlockfile operations. 43 | UserModeLock = 256, 44 | 45 | /// 46 | /// Enable methods in , which require this library to maintain a special 47 | /// handle while the file system is mounted. 48 | /// Without this flag, the methods in that inner class always return false if invoked. 49 | /// 50 | EnableNotificationAPI = 512, 51 | 52 | /// 53 | /// Enable Case sensitive path. 54 | /// By default all path are case insensitive. 55 | /// For case sensitive: \\dir\\File and \\diR\\file are different files 56 | /// but for case insensitive they are the same. 57 | /// 58 | CaseSensitive = 1024, 59 | 60 | /// 61 | /// Enables unmounting of network drives via file explorer 62 | /// 63 | EnableNetworkUnmount = 2048, 64 | 65 | /// 66 | /// Forward the kernel driver global and volume logs to the userland 67 | /// 68 | DispatchDriverLogs = 4096, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /DokanNet/DokanResult.cs: -------------------------------------------------------------------------------- 1 | namespace DokanNet; 2 | 3 | /// 4 | /// Defines common result status codes in for %Dokan 5 | /// operations. 6 | /// 7 | public static class DokanResult 8 | { 9 | /// 10 | /// Success - The operation completed successfully. 11 | /// 12 | public const NtStatus Success = NtStatus.Success; 13 | 14 | /// 15 | /// Error - Incorrect function. 16 | /// 17 | public const NtStatus Error = NtStatus.Error; 18 | 19 | /// 20 | /// Error - The system cannot find the file specified. 21 | /// 22 | public const NtStatus FileNotFound = NtStatus.ObjectNameNotFound; 23 | 24 | /// 25 | /// Error - The system cannot find the path specified. 26 | /// 27 | public const NtStatus PathNotFound = NtStatus.ObjectPathNotFound; 28 | 29 | /// 30 | /// Error - Access is denied. 31 | /// 32 | public const NtStatus AccessDenied = NtStatus.AccessDenied; 33 | 34 | /// 35 | /// Error - The handle is invalid. 36 | /// 37 | public const NtStatus InvalidHandle = NtStatus.InvalidHandle; 38 | 39 | /// 40 | /// Warning - The device is not ready. 41 | /// 42 | public const NtStatus NotReady = NtStatus.DeviceBusy; 43 | 44 | /// 45 | /// Error - The process cannot access the file because it is being used 46 | /// by another process. 47 | /// 48 | public const NtStatus SharingViolation = NtStatus.SharingViolation; 49 | 50 | /// 51 | /// Error - The file exists. 52 | /// 53 | public const NtStatus FileExists = NtStatus.ObjectNameCollision; 54 | 55 | /// 56 | /// Error - There is not enough space on the disk. 57 | /// 58 | public const NtStatus DiskFull = NtStatus.DiskFull; 59 | 60 | /// 61 | /// Error - This function is not supported on this system. 62 | /// 63 | public const NtStatus NotImplemented = NtStatus.NotImplemented; 64 | 65 | /// 66 | /// Error - The data area passed to a system call is too small. 67 | /// 68 | public const NtStatus BufferTooSmall = NtStatus.BufferTooSmall; 69 | 70 | /// 71 | /// Warning - The data area passed to a system call is too small. 72 | /// 73 | public const NtStatus BufferOverflow = NtStatus.BufferOverflow; 74 | 75 | /// 76 | /// Error - The filename, directory name, or volume label syntax is 77 | /// incorrect. 78 | /// 79 | public const NtStatus InvalidName = NtStatus.ObjectNameInvalid; 80 | 81 | /// 82 | /// Error - The directory is not empty. 83 | /// 84 | public const NtStatus DirectoryNotEmpty = NtStatus.DirectoryNotEmpty; 85 | 86 | /// 87 | /// Error - Cannot create a file when that file already exists. 88 | /// 89 | public const NtStatus AlreadyExists = NtStatus.ObjectNameCollision; 90 | 91 | /// 92 | /// Error - An exception occurred in the service when handling the 93 | /// control request. 94 | /// 95 | public const NtStatus InternalError = NtStatus.InternalError; 96 | 97 | /// 98 | /// Error - A required privilege is not held by the client. 99 | /// 100 | public const NtStatus PrivilegeNotHeld = NtStatus.PrivilegeNotHeld; 101 | 102 | /// 103 | /// Error - The requested operation was unsuccessful. 104 | /// 105 | public const NtStatus Unsuccessful = NtStatus.Unsuccessful; 106 | 107 | /// 108 | /// Error - A directory semantics call was made but the accessed file was not a directory. 109 | /// 110 | public const NtStatus NotADirectory = NtStatus.NotADirectory; 111 | 112 | /// 113 | /// Error - The parameter is incorrect. 114 | /// 115 | public const NtStatus InvalidParameter = NtStatus.InvalidParameter; 116 | } -------------------------------------------------------------------------------- /DokanNet/DokanStatus.cs: -------------------------------------------------------------------------------- 1 | namespace DokanNet 2 | { 3 | /// 4 | /// Error codes returned by DokanMain. 5 | /// 6 | public enum DokanStatus : int 7 | { 8 | /// 9 | /// Dokan mount succeed. 10 | /// 11 | Success = 0, 12 | 13 | /// 14 | /// Dokan mount error. 15 | /// 16 | Error = -1, 17 | 18 | /// 19 | /// Dokan mount failed - Bad drive letter. 20 | /// 21 | DriveLetterError = -2, 22 | 23 | /// 24 | /// Dokan mount failed - Can't install driver. 25 | /// 26 | DriverInstallError = -3, 27 | 28 | /// 29 | /// Dokan mount failed - Driver answer that something is wrong. 30 | /// 31 | StartError = -4, 32 | 33 | /// 34 | /// Dokan mount failed. 35 | /// Can't assign a drive letter or mount point. 36 | /// Probably already used by another volume. 37 | /// 38 | MountError = -5, 39 | 40 | /// 41 | /// Dokan mount failed. 42 | /// Mount point is invalid. 43 | /// 44 | MountPointError = -6, 45 | 46 | /// 47 | /// Dokan mount failed. 48 | /// Requested an incompatible version. 49 | /// 50 | VersionError = -7 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /DokanNet/FileInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace DokanNet 7 | { 8 | /// 9 | /// Used to provide file information to %Dokan during operations by 10 | /// - 11 | /// - 12 | /// - 13 | /// - . 14 | /// 15 | [StructLayout(LayoutKind.Auto)] 16 | [DebuggerDisplay("{FileName}, {Length}, {CreationTime}, {LastWriteTime}, {LastAccessTime}, {Attributes}")] 17 | public struct FileInformation 18 | { 19 | /// 20 | /// Gets or sets the name of the file or directory. 21 | /// required the file path 22 | /// when other operations only need the file name. 23 | /// 24 | public string FileName { get; set; } 25 | 26 | /// 27 | /// Gets or sets the for the file or directory. 28 | /// 29 | public FileAttributes Attributes { get; set; } 30 | 31 | /// 32 | /// Gets or sets the creation time of the file or directory. 33 | /// If equal to null, the value will not be set or the file has no creation time. 34 | /// 35 | public DateTime? CreationTime { get; set; } 36 | 37 | /// 38 | /// Gets or sets the last access time of the file or directory. 39 | /// If equal to null, the value will not be set or the file has no last access time. 40 | /// 41 | public DateTime? LastAccessTime { get; set; } 42 | 43 | /// 44 | /// Gets or sets the last write time of the file or directory. 45 | /// If equal to null, the value will not be set or the file has no last write time. 46 | /// 47 | public DateTime? LastWriteTime { get; set; } 48 | 49 | /// 50 | /// Gets or sets the length of the file. 51 | /// 52 | public long Length { get; set; } 53 | } 54 | } -------------------------------------------------------------------------------- /DokanNet/FileSystemFeatures.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DokanNet; 4 | 5 | /// 6 | /// Defines feature flags for the file system. 7 | /// 8 | /// \see File System Attribute Extensions (MSDN) 9 | [Flags] 10 | #pragma warning disable 3009 11 | public enum FileSystemFeatures : uint 12 | #pragma warning restore 3009 13 | { 14 | /// 15 | /// No features defined. 16 | /// 17 | None = 0, 18 | 19 | /// 20 | /// The file system supports case-sensitive file names. 21 | /// 22 | CaseSensitiveSearch = 1, 23 | 24 | /// 25 | /// The file system preserves the case of file names when it stores the name on disk. 26 | /// 27 | CasePreservedNames = 2, 28 | 29 | /// 30 | /// The file system supports Unicode in file names. 31 | /// 32 | UnicodeOnDisk = 4, 33 | 34 | /// 35 | /// The file system preserves and enforces access control lists. 36 | /// 37 | PersistentAcls = 8, 38 | 39 | /// 40 | /// The file system supports per-user quotas. 41 | /// 42 | VolumeQuotas = 0x20, 43 | 44 | /// 45 | /// The file system supports sparse files. 46 | /// 47 | /// This feature allows programs to create very large files, but to consume disk space only as needed. 48 | /// A sparse file is a file that contains much of zeros, and a file system that support 49 | /// this are removing repeating zeros to save space. 50 | /// 51 | /// \see Sparse Files (MSDN) 52 | SupportsSparseFiles = 0x40, 53 | 54 | /// 55 | /// The file system supports reparse points. 56 | /// 57 | /// Programs can trap open operations against objects in the file system and run their 58 | /// own code before returning file data. This feature can be used to extend file 59 | /// system features such as mount points, which you can use to redirect data read 60 | /// and written from a folder to another volume or physical disk. 61 | /// 62 | SupportsReparsePoints = 0x80, 63 | 64 | /// 65 | /// The file system supports remote storage. 66 | /// 67 | SupportsRemoteStorage = 0x100, 68 | 69 | /// 70 | /// Volume is a compressed volume. This flag is incompatible with FILE_FILE_COMPRESSION. 71 | /// This does not affect how data is transferred over the network. 72 | /// 73 | VolumeIsCompressed = 0x00008000, 74 | 75 | /// 76 | /// File system supports object identifiers. 77 | /// 78 | SupportsObjectIDs = 0x00010000, 79 | 80 | /// 81 | /// File system supports encryption. 82 | /// 83 | SupportsEncryption = 0x00020000, 84 | 85 | /// 86 | /// File system supports multiple named data streams for a file. 87 | /// 88 | /// The unnamed data stream is the stream where the content in the file is stored. 89 | /// A named stream is a stream where extra data can be stored. The data is connected to the file 90 | /// and if the file is moved, the data is copied to the new place if that file system support it. 91 | /// \see Alternate Data Streams in NTFS (TechNet) 92 | /// 93 | NamedStreams = 0x00040000, 94 | 95 | /// 96 | /// Specified volume is read-only. 97 | /// 98 | ReadOnlyVolume = 0x00080000, 99 | 100 | /// 101 | /// Specified volume can be written to one time only. The write MUST be performed in sequential order. 102 | /// 103 | SequentialWriteOnce = 0x00100000, 104 | 105 | /// 106 | /// The file system supports transaction processing to group changes to files into a transaction. 107 | /// The transaction will guarantee that all changes happens, or none of them. 108 | /// \see About KTM (MSDN) 109 | /// 110 | SupportsTransactions = 0x00200000 111 | } -------------------------------------------------------------------------------- /DokanNet/FindFileInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace DokanNet; 7 | 8 | /// 9 | /// Used to provide file information to %Dokan during operations by 10 | /// - 11 | /// - 12 | /// - 13 | /// - . 14 | /// 15 | [StructLayout(LayoutKind.Auto)] 16 | [DebuggerDisplay("{FileName}, {Length}, {CreationTime}, {LastWriteTime}, {LastAccessTime}, {Attributes}")] 17 | public struct FindFileInformation 18 | { 19 | /// 20 | /// Gets or sets the name of the file or directory. 21 | /// required the file path 22 | /// when other operations only need the file name. 23 | /// 24 | public ReadOnlyMemory FileName { get; set; } 25 | 26 | /// 27 | /// Gets or sets the for the file or directory. 28 | /// 29 | public FileAttributes Attributes { get; set; } 30 | 31 | /// 32 | /// Gets or sets the creation time of the file or directory. 33 | /// If equal to null, the value will not be set or the file has no creation time. 34 | /// 35 | public DateTime? CreationTime { get; set; } 36 | 37 | /// 38 | /// Gets or sets the last access time of the file or directory. 39 | /// If equal to null, the value will not be set or the file has no last access time. 40 | /// 41 | public DateTime? LastAccessTime { get; set; } 42 | 43 | /// 44 | /// Gets or sets the last write time of the file or directory. 45 | /// If equal to null, the value will not be set or the file has no last write time. 46 | /// 47 | public DateTime? LastWriteTime { get; set; } 48 | 49 | /// 50 | /// Gets or sets the length of the file. 51 | /// 52 | public long Length { get; set; } 53 | 54 | /// 55 | /// Short file name in 8.3 format. 56 | /// 57 | public ReadOnlyMemory ShortFileName { get; set; } 58 | 59 | /// 60 | /// Returns value of property converted to a . 61 | /// 62 | /// 63 | public override readonly string ToString() => FileName.ToString(); 64 | } 65 | -------------------------------------------------------------------------------- /DokanNet/IDokanFileInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Principal; 2 | 3 | namespace DokanNet 4 | { 5 | /// 6 | /// %Dokan file information interface. 7 | /// 8 | /// 9 | /// This interface can be inherited in order to testunit user implementation. 10 | /// 11 | public interface IDokanFileInfo 12 | { 13 | /// 14 | /// Gets or sets context that can be used to carry information between operation. 15 | /// The Context can carry whatever type like , struct, int, 16 | /// or internal reference that will help the implementation understand the request context of the event. 17 | /// 18 | object? Context { get; set; } 19 | 20 | /// 21 | /// Gets or sets a value indicating whether the file has to be delete 22 | /// during the event. 23 | /// 24 | bool DeletePending { get; set; } 25 | 26 | /// 27 | /// Gets or sets a value indicating whether it requesting a directory 28 | /// file. Must be set in if 29 | /// the file appear to be a folder. 30 | /// 31 | bool IsDirectory { get; set; } 32 | 33 | /// 34 | /// Read or write directly from data source without cache. 35 | /// 36 | bool NoCache { get; } 37 | 38 | /// 39 | /// Read or write is paging IO. 40 | /// 41 | bool PagingIo { get; } 42 | 43 | /// 44 | /// Process id for the thread that originally requested a given I/O 45 | /// operation. 46 | /// 47 | int ProcessId { get; } 48 | 49 | /// 50 | /// Read or write is synchronous IO. 51 | /// 52 | bool SynchronousIo { get; } 53 | 54 | /// 55 | /// If true, write to the current end of file instead 56 | /// of using the Offset parameter. 57 | /// 58 | bool WriteToEndOfFile { get; } 59 | 60 | /// 61 | /// This method needs to be called in . 62 | /// 63 | /// An with the access token, 64 | /// -or- null if the operation was not successful. 65 | WindowsIdentity GetRequestor(); 66 | 67 | /// 68 | /// Extends the time out of the current IO operation in driver. 69 | /// 70 | /// Number of milliseconds to extend with. 71 | /// If the operation was successful. 72 | bool TryResetTimeout(int milliseconds); 73 | } 74 | } -------------------------------------------------------------------------------- /DokanNet/IDokanOperationsUnsafe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DokanNet 4 | { 5 | /// 6 | /// This is a sub-interface of that can optionally be implemented 7 | /// to get access to the raw, unmanaged buffers for ReadFile() and WriteFile() for performance optimization. 8 | /// Marshalling the unmanaged buffers to and from byte[] arrays for every call of these APIs incurs an extra copy 9 | /// that can be avoided by reading from or writing directly to the unmanaged buffers. 10 | /// 11 | /// Implementation of this interface is optional. If it is implemented, the overloads of 12 | /// Read/WriteFile(IntPtr, length) will be called instead of Read/WriteFile(byte[]). The caller can fill or read 13 | /// from the unmanaged API with Marshal.Copy, Buffer.MemoryCopy or similar. 14 | /// 15 | public interface IDokanOperationsUnsafe : IDokanOperations 16 | { 17 | /// 18 | /// ReadFile callback on the file previously opened in . 19 | /// It can be called by different thread at the same time, 20 | /// therefore the read has to be thread safe. 21 | /// 22 | /// File path requested by the Kernel on the FileSystem. 23 | /// Read buffer that has to be fill with the read result. 24 | /// The size of 'buffer' in bytes. 25 | /// The buffer size depends of the read size requested by the kernel. 26 | /// Total number of bytes that has been read. 27 | /// Offset from where the read has to be proceed. 28 | /// An with information about the file or directory. 29 | /// or appropriate to the request result. 30 | /// 31 | NtStatus ReadFile(string fileName, IntPtr buffer, uint bufferLength, out int bytesRead, long offset, IDokanFileInfo info); 32 | 33 | /// 34 | /// WriteFile callback on the file previously opened in 35 | /// It can be called by different thread at the same time, 36 | /// therefore the write/context has to be thread safe. 37 | /// 38 | /// File path requested by the Kernel on the FileSystem. 39 | /// Data that has to be written. 40 | /// The size of 'buffer' in bytes. 41 | /// Total number of bytes that has been write. 42 | /// Offset from where the write has to be proceed. 43 | /// An with information about the file or directory. 44 | /// or appropriate to the request result. 45 | /// 46 | NtStatus WriteFile(string fileName, IntPtr buffer, uint bufferLength, out int bytesWritten, long offset, IDokanFileInfo info); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DokanNet/Logging/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Globalization; 4 | using System.Threading.Tasks; 5 | 6 | namespace DokanNet.Logging 7 | { 8 | /// 9 | /// Log to the console. 10 | /// 11 | public class ConsoleLogger : ILogger, IDisposable 12 | { 13 | private readonly string _loggerName; 14 | private readonly DateTimeFormatInfo? _dateTimeFormatInfo; 15 | private readonly BlockingCollection<(string Message, ConsoleColor Color)> _PendingLogs = []; 16 | 17 | private readonly Task? _WriterTask = null; 18 | private bool _disposed; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// Optional name to be added to each log line. 24 | /// An object that supplies format information for DateTime. 25 | public ConsoleLogger(string loggerName = "", DateTimeFormatInfo? dateTimeFormatInfo = null) 26 | { 27 | _loggerName = loggerName; 28 | _dateTimeFormatInfo = dateTimeFormatInfo; 29 | _WriterTask = Task.Factory.StartNew(() => 30 | { 31 | foreach (var (Message, Color) in _PendingLogs.GetConsumingEnumerable()) 32 | { 33 | WriteMessage(Message, Color); 34 | } 35 | }); 36 | } 37 | 38 | /// 39 | public bool DebugEnabled => true; 40 | 41 | /// 42 | public void Debug(string message, params object[] args) 43 | { 44 | EnqueueMessage(Console.ForegroundColor, message, args); 45 | } 46 | 47 | /// 48 | public void Info(string message, params object[] args) 49 | { 50 | EnqueueMessage(Console.ForegroundColor, message, args); 51 | } 52 | 53 | /// 54 | public void Warn(string message, params object[] args) 55 | { 56 | EnqueueMessage(ConsoleColor.DarkYellow, message, args); 57 | } 58 | 59 | /// 60 | public void Error(string message, params object[] args) 61 | { 62 | EnqueueMessage(ConsoleColor.Red, message, args); 63 | } 64 | 65 | /// 66 | public void Fatal(string message, params object[] args) 67 | { 68 | EnqueueMessage(ConsoleColor.Red, message, args); 69 | } 70 | 71 | private void EnqueueMessage(ConsoleColor newColor, string message, params object[] args) 72 | { 73 | if (args.Length > 0) 74 | { 75 | message = string.Format(message, args); 76 | } 77 | 78 | _PendingLogs.Add((message, newColor)); 79 | } 80 | 81 | private void WriteMessage(string message, ConsoleColor newColor) 82 | { 83 | lock (Console.Out) // we only need this lock because we want to have more than one logger in this version 84 | { 85 | var origForegroundColor = Console.ForegroundColor; 86 | Console.ForegroundColor = newColor; 87 | Console.WriteLine(message.FormatMessageForLogging(true, loggerName: _loggerName, dateTimeFormatInfo: _dateTimeFormatInfo)); 88 | Console.ForegroundColor = origForegroundColor; 89 | } 90 | } 91 | 92 | /// 93 | /// Dispose the object from the native resources. 94 | /// 95 | /// Whether it was call by 96 | protected virtual void Dispose(bool disposing) 97 | { 98 | if (!_disposed) 99 | { 100 | if (disposing) 101 | { 102 | // dispose managed state (managed objects) 103 | _PendingLogs.CompleteAdding(); 104 | _WriterTask?.Wait(); 105 | } 106 | 107 | // free unmanaged resources (unmanaged objects) and override finalizer 108 | // set large fields to null 109 | _disposed = true; 110 | } 111 | } 112 | 113 | /// 114 | /// Dispose the object from the native resources. 115 | /// 116 | public void Dispose() 117 | { 118 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 119 | Dispose(disposing: true); 120 | GC.SuppressFinalize(this); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /DokanNet/Logging/DebugViewLogger.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace DokanNet.Logging 4 | { 5 | /// 6 | /// Write log using OutputDebugString 7 | /// 8 | /// 9 | /// To see the output in visual studio 10 | /// Project + %Properties, Debug tab, check "Enable unmanaged code debugging". 11 | /// 12 | public class DebugViewLogger : ILogger 13 | { 14 | private readonly string _loggerName; 15 | private readonly DateTimeFormatInfo? _dateTimeFormatInfo; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// Optional name to be added to each log line. 21 | /// An object that supplies format information for DateTime. 22 | public DebugViewLogger(string loggerName = "", DateTimeFormatInfo? dateTimeFormatInfo = null) 23 | { 24 | _loggerName = loggerName; 25 | _dateTimeFormatInfo = dateTimeFormatInfo; 26 | } 27 | 28 | /// 29 | public bool DebugEnabled => true; 30 | 31 | /// 32 | public void Debug(string message, params object[] args) 33 | { 34 | WriteMessageToDebugView("debug", message, args); 35 | } 36 | 37 | /// 38 | public void Info(string message, params object[] args) 39 | { 40 | WriteMessageToDebugView("info", message, args); 41 | } 42 | 43 | /// 44 | public void Warn(string message, params object[] args) 45 | { 46 | WriteMessageToDebugView("warn", message, args); 47 | } 48 | 49 | /// 50 | public void Error(string message, params object[] args) 51 | { 52 | WriteMessageToDebugView("error", message, args); 53 | } 54 | 55 | /// 56 | public void Fatal(string message, params object[] args) 57 | { 58 | WriteMessageToDebugView("fatal", message, args); 59 | } 60 | 61 | private void WriteMessageToDebugView(string category, string message, params object[] args) 62 | { 63 | if (args?.Length > 0) 64 | { 65 | message = string.Format(message, args); 66 | } 67 | 68 | System.Diagnostics.Debug.WriteLine(message.FormatMessageForLogging(category, _loggerName, _dateTimeFormatInfo)); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /DokanNet/Logging/ILogger.cs: -------------------------------------------------------------------------------- 1 | namespace DokanNet.Logging 2 | { 3 | /// 4 | /// The %Logger interface. 5 | /// 6 | public interface ILogger 7 | { 8 | /// 9 | /// Gets a value indicating whether the logger wishes to receive debug messages. 10 | /// 11 | bool DebugEnabled { get; } 12 | 13 | /// 14 | /// Log a debug message 15 | /// 16 | /// The message to write to the log 17 | /// Arguments to use to format the 18 | void Debug(string message, params object[] args); 19 | 20 | /// 21 | /// Log a info message 22 | /// 23 | /// The message to write to the log 24 | /// Arguments to use to format the 25 | void Info(string message, params object[] args); 26 | 27 | /// 28 | /// Log a warning message 29 | /// 30 | /// The message to write to the log 31 | /// Arguments to use to format the 32 | void Warn(string message, params object[] args); 33 | 34 | /// 35 | /// Log a error message 36 | /// 37 | /// The message to write to the log 38 | /// Arguments to use to format the 39 | void Error(string message, params object[] args); 40 | 41 | /// 42 | /// Log a fatal error message 43 | /// 44 | /// The message to write to the log 45 | /// Arguments to use to format the 46 | void Fatal(string message, params object[] args); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DokanNet/Logging/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DokanNet.Logging 4 | { 5 | /// 6 | /// Handle log messages with callbacks 7 | /// 8 | public class Logger : ILogger 9 | { 10 | private readonly Action _debug; 11 | private readonly Action _info; 12 | private readonly Action _warn; 13 | private readonly Action _error; 14 | private readonly Action _fatal; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// An that are called when a debug log message are arriving. 20 | /// An that are called when a information log message are arriving 21 | /// An that are called when a warning log message are arriving 22 | /// An that are called when a error log message are arriving 23 | /// An that are called when a fatal error log message are arriving 24 | public Logger( 25 | Action debug, 26 | Action info, 27 | Action warn, 28 | Action error, 29 | Action fatal) 30 | { 31 | _debug = debug; 32 | _info = info; 33 | _warn = warn; 34 | _error = error; 35 | _fatal = fatal; 36 | } 37 | 38 | /// 39 | public bool DebugEnabled => _debug != null; 40 | 41 | /// 42 | public void Debug(string message, params object[] args) 43 | { 44 | _debug(message, args); 45 | } 46 | 47 | /// 48 | public void Info(string message, params object[] args) 49 | { 50 | _info(message, args); 51 | } 52 | 53 | /// 54 | public void Warn(string message, params object[] args) 55 | { 56 | _warn(message, args); 57 | } 58 | 59 | /// 60 | public void Error(string message, params object[] args) 61 | { 62 | _error(message, args); 63 | } 64 | 65 | /// 66 | public void Fatal(string message, params object[] args) 67 | { 68 | _fatal(message, args); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /DokanNet/Logging/LoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace DokanNet.Logging 6 | { 7 | /// 8 | /// Extension functions to log messages. 9 | /// 10 | public static class LoggerExtensions 11 | { 12 | /// 13 | /// Format a log message. 14 | /// 15 | /// Message to format. 16 | /// Optional category to add to the log message. 17 | /// Optional log name to at to the log message. 18 | /// An object that supplies format information for DateTime. 19 | /// A formated log message. 20 | public static string FormatMessageForLogging( 21 | this string message, 22 | string? category = null, 23 | string loggerName = "", 24 | DateTimeFormatInfo? dateTimeFormatInfo = null) 25 | { 26 | return message.FormatMessageForLogging(false, category, loggerName, dateTimeFormatInfo); 27 | } 28 | 29 | /// 30 | /// Format a log message. 31 | /// 32 | /// Message to format. 33 | /// If date and time shout be added to the log message. 34 | /// Optional category to add to the log message. 35 | /// Optional log name to at to the log message. 36 | /// An object that supplies format information for DateTime. 37 | /// A formated log message. 38 | public static string FormatMessageForLogging( 39 | this string message, 40 | bool addDateTime = false, 41 | string? category = null, 42 | string loggerName = "", 43 | DateTimeFormatInfo? dateTimeFormatInfo = null) 44 | { 45 | var stringBuilder = new StringBuilder(); 46 | if (addDateTime) 47 | { 48 | stringBuilder.AppendFormat("{0} - ", DateTime.Now.ToString((IFormatProvider?)dateTimeFormatInfo ?? CultureInfo.InvariantCulture)); 49 | } 50 | 51 | if (!string.IsNullOrEmpty(loggerName)) 52 | { 53 | stringBuilder.Append(loggerName); 54 | } 55 | 56 | if (!string.IsNullOrEmpty(category)) 57 | { 58 | stringBuilder.Append($"{category} "); 59 | } 60 | 61 | stringBuilder.Append(message); 62 | return stringBuilder.ToString(); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /DokanNet/Logging/NullLogger.cs: -------------------------------------------------------------------------------- 1 | namespace DokanNet.Logging 2 | { 3 | /// 4 | /// Ignore all log messages. 5 | /// 6 | public class NullLogger : ILogger 7 | { 8 | /// 9 | public bool DebugEnabled => false; 10 | 11 | /// 12 | public void Debug(string message, params object[] args) 13 | { 14 | } 15 | 16 | /// 17 | public void Error(string message, params object[] args) 18 | { 19 | } 20 | 21 | /// 22 | public void Fatal(string message, params object[] args) 23 | { 24 | } 25 | 26 | /// 27 | public void Info(string message, params object[] args) 28 | { 29 | } 30 | 31 | /// 32 | public void Warn(string message, params object[] args) 33 | { 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /DokanNet/Logging/TraceLogger.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace DokanNet.Logging 4 | { 5 | /// 6 | /// Write all log messages to the . 7 | /// 8 | public class TraceLogger : ILogger 9 | { 10 | /// 11 | public bool DebugEnabled => true; 12 | 13 | /// 14 | public void Debug(string message, params object[] args) 15 | { 16 | Trace.TraceInformation(message, args); 17 | } 18 | 19 | /// 20 | public void Info(string message, params object[] args) 21 | { 22 | Trace.TraceInformation(message, args); 23 | } 24 | 25 | /// 26 | public void Warn(string message, params object[] args) 27 | { 28 | Trace.TraceWarning(message, args); 29 | } 30 | 31 | /// 32 | public void Error(string message, params object[] args) 33 | { 34 | Trace.TraceError(message, args); 35 | } 36 | 37 | /// 38 | public void Fatal(string message, params object[] args) 39 | { 40 | Trace.TraceError(message, args); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /DokanNet/MockDokanFileInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Versioning; 2 | using System.Security.Principal; 3 | using DokanNet.Native; 4 | using static DokanNet.FormatProviders; 5 | 6 | #pragma warning disable 649,169 7 | 8 | namespace DokanNet 9 | { 10 | /// 11 | /// Mockable Dokan file information on the current operation. 12 | /// 13 | /// 14 | /// Because cannot be instantiated in C#, it's very difficult to write 15 | /// test harnesses for unit testing. This class implements the same public interface so it's 16 | /// possible to mock it, such that the implementor of IDokanOperations can essentially act like 17 | /// the dokany kernel driver and call into that implementation to verify correct behavior. It 18 | /// also has support methods available to cause it (and the Dokan.Net library) to behave in certain 19 | /// ways useful for testing all potential returns, both success and failure. 20 | /// 21 | #if NET5_0_OR_GREATER 22 | [SupportedOSPlatform("windows")] 23 | #endif 24 | public sealed class MockDokanFileInfo : IDokanFileInfo 25 | { 26 | /// 27 | /// A structure used to help the kernel notification functions work. 28 | /// 29 | private DOKAN_OPTIONS _dokanOptions = new DOKAN_OPTIONS(); 30 | 31 | /// 32 | /// This must be set to a potentially valid path. Examples might be @"M:\\" or @"C:\\JunctionPoint". 33 | /// 34 | /// The trailing backslash is not optional for drive letters, and must be omitted for paths. 35 | public string? MountPoint 36 | { 37 | get => _dokanOptions.MountPoint; 38 | set => _dokanOptions.MountPoint = value; 39 | } 40 | 41 | /// 42 | /// Gets or sets context that can be used to carry information between operation. 43 | /// The Context can carry an arbitrary type, like , struct, int, 44 | /// or internal reference that will help the implementation understand the request context of the event. 45 | /// 46 | public object? Context 47 | { 48 | get; set; 49 | } 50 | 51 | /// 52 | /// Process id for the thread that originally requested a given I/O 53 | /// operation. 54 | /// 55 | public int ProcessId { get; set; } 56 | 57 | /// 58 | /// Gets or sets a value indicating whether a filename references a directory. 59 | /// Must be set in if the file is to 60 | /// appear to be a folder. 61 | /// 62 | public bool IsDirectory { get; set; } 63 | 64 | /// 65 | /// Gets or sets a value indicating whether the file has to be deleted 66 | /// during the event. 67 | /// 68 | public bool DeletePending { get; set; } 69 | 70 | /// 71 | /// Read or write is paging IO. 72 | /// 73 | public bool PagingIo { get; set; } 74 | 75 | /// 76 | /// Read or write is synchronous IO. 77 | /// 78 | public bool SynchronousIo { get; set; } 79 | 80 | /// 81 | /// Read or write directly from data source without cache. 82 | /// 83 | public bool NoCache { get; set; } 84 | 85 | /// 86 | /// If true, write to the current end of file instead 87 | /// of using the Offset parameter. 88 | /// 89 | public bool WriteToEndOfFile { get; set; } 90 | 91 | /// 92 | /// Set this to null if you want to test against token unavailability. 93 | /// 94 | /// Initialized to the current process token, which is the only 95 | /// token a standard user account can get. 96 | public WindowsIdentity WhatGetRequestorShouldReturn = WindowsIdentity.GetCurrent(); 97 | 98 | /// 99 | /// This method needs to be called in to 100 | /// determine what account and what privileges are available to the filesystem client. 101 | /// 102 | /// An with the access token, 103 | /// -or- null if the operation was not successful. 104 | /// This Mock implementation returns . 105 | /// 106 | public WindowsIdentity GetRequestor() => WhatGetRequestorShouldReturn; 107 | 108 | /// 109 | /// Set this to false if you want to test against TryResetTimeout failure 110 | /// 111 | public bool WhatTryResetTimeoutShouldReturn { get; set; } 112 | 113 | /// 114 | /// Extends the time out of the current IO operation in driver. 115 | /// 116 | /// Number of milliseconds to extend with. 117 | /// true if the operation was successful. 118 | /// This Mock implementation returns . 119 | /// 120 | public bool TryResetTimeout(int milliseconds) => WhatTryResetTimeoutShouldReturn; 121 | 122 | /// Returns a string that represents the current object. 123 | /// A string that represents the current object. 124 | public override string ToString() 125 | { 126 | return 127 | DokanFormat( 128 | $"mock: {{{Context}, {DeletePending}, {IsDirectory}, {NoCache}, {PagingIo}, #{ProcessId}, {SynchronousIo}, {WriteToEndOfFile}}}"); 129 | } 130 | } 131 | } 132 | 133 | #pragma warning restore 649, 169 -------------------------------------------------------------------------------- /DokanNet/Native/BY_HANDLE_FILE_INFORMATION.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; 3 | 4 | namespace DokanNet.Native; 5 | 6 | /// 7 | /// Contains information that the function retrieves. 8 | /// 9 | /// 10 | /// The identifier that is stored in the nFileIndexHigh and nFileIndexLow members is called the file ID. 11 | /// Support for file IDs is file system-specific. File IDs are not guaranteed to be unique over time, 12 | /// because file systems are free to reuse them. In some cases, the file ID for a file can change over time. 13 | /// 14 | /// In the FAT file system, the file ID is generated from the first cluster of the containing directory 15 | /// and the byte offset within the directory of the entry for the file. Some defragmentation products 16 | /// change this byte offset. (Windows in-box defragmentation does not.) Thus, a FAT file ID can change 17 | /// over time.Renaming a file in the FAT file system can also change the file ID, but only if the new 18 | /// file name is longer than the old one. 19 | /// 20 | /// In the NTFS file system, a file keeps the same file ID until it is deleted. You can replace one file 21 | /// with another file without changing the file ID by using the ReplaceFile function. However, the file ID 22 | /// of the replacement file, not the replaced file, is retained as the file ID of the resulting file. 23 | /// 24 | /// Not all file systems can record creation and last access time, and not all file systems record them 25 | /// in the same manner. For example, on a Windows FAT file system, create time has a resolution of 26 | /// 10 milliseconds, write time has a resolution of 2 seconds, and access time has a resolution of 27 | /// 1 day (the access date). On the NTFS file system, access time has a resolution of 1 hour. 28 | /// 29 | /// \see BY_HANDLE_FILE_INFORMATION structure (MSDN) 30 | [StructLayout(LayoutKind.Sequential, Pack = 4)] 31 | internal struct BY_HANDLE_FILE_INFORMATION 32 | { 33 | /// 34 | /// The file attributes. For possible values and their descriptions. 35 | /// 36 | public uint dwFileAttributes; 37 | 38 | /// 39 | /// A structure that specifies when a file or directory is created. 40 | /// If the underlying file system does not support creation time, this member is zero (0). 41 | /// 42 | public FILETIME ftCreationTime; 43 | 44 | /// 45 | /// A structure. 46 | /// For a file, the structure specifies the last time that a file is read from or written to. 47 | /// For a directory, the structure specifies when the directory is created. 48 | /// For both files and directories, the specified date is correct, but the time of day is always set to midnight. 49 | /// If the underlying file system does not support the last access time, this member is zero (0). 50 | /// 51 | public FILETIME ftLastAccessTime; 52 | 53 | /// 54 | /// A structure. 55 | /// For a file, the structure specifies the last time that a file is written to. 56 | /// For a directory, the structure specifies when the directory is created. 57 | /// If the underlying file system does not support the last write time, this member is zero (0). 58 | /// 59 | public FILETIME ftLastWriteTime; 60 | 61 | /// 62 | /// The serial number of the volume that contains a file. 63 | /// 64 | public uint dwVolumeSerialNumber; 65 | 66 | /// 67 | /// The high-order part of the file size. 68 | /// 69 | public uint nFileSizeHigh; 70 | 71 | /// 72 | /// The low-order part of the file size. 73 | /// 74 | public uint nFileSizeLow; 75 | 76 | /// 77 | /// The number of links to this file. 78 | /// For the FAT file system this member is always 1. 79 | /// For the NTFS file system, it can be more than 1. 80 | /// 81 | public int dwNumberOfLinks; 82 | 83 | /// 84 | /// The high-order part of a unique identifier that is associated with a file. 85 | /// For more information, see . 86 | /// 87 | public uint nFileIndexHigh; 88 | 89 | /// 90 | /// The low-order part of a unique identifier that is associated with a file. 91 | /// 92 | /// The identifier (low and high parts) and the volume serial number uniquely 93 | /// identify a file on a single computer. To determine whether two open handles 94 | /// represent the same file, combine the identifier and the volume serial number 95 | /// for each file and compare them. 96 | /// 97 | /// The ReFS file system, introduced with Windows Server 2012, includes 128-bit 98 | /// file identifiers. To retrieve the 128-bit file identifier use the 99 | /// GetFileInformationByHandleEx function with FileIdInfo to retrieve 100 | /// the FILE_ID_INFO structure. The 64-bit identifier in this structure is not 101 | /// guaranteed to be unique on ReFS. 102 | /// 103 | public uint nFileIndexLow; 104 | } 105 | -------------------------------------------------------------------------------- /DokanNet/Native/DOKAN_OPTIONS.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace DokanNet.Native; 4 | 5 | /// 6 | /// Dokan mount options used to describe dokan device behaviour 7 | /// 8 | /// 9 | /// This is the same structure as PDOKAN_OPTIONS (dokan.h) in the C version of Dokan. 10 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)] 11 | public sealed class DOKAN_OPTIONS 12 | { 13 | /// 14 | /// Version of the dokan features requested (version "123" is equal to Dokan version 1.2.3). 15 | /// 16 | public ushort Version { get; set; } 17 | 18 | /// 19 | /// Only use a single thread to process events. This is highly not recommended as can easily create a bottleneck. 20 | /// 21 | [field: MarshalAs(UnmanagedType.U1)] 22 | public bool SingleThread { get; set; } 23 | 24 | /// 25 | /// Features enable for the mount. See . 26 | /// 27 | public DokanOptions Options { get; set; } 28 | 29 | /// 30 | /// FileSystem can store anything here. 31 | /// 32 | public ulong GlobalContext { get; set; } 33 | 34 | /// 35 | /// Mount point. 36 | /// Can be M:\\(drive letter) or C:\\mount\\dokan (path in NTFS). 37 | /// 38 | [field: MarshalAs(UnmanagedType.LPWStr)] 39 | public string? MountPoint { get; set; } 40 | 41 | /// 42 | /// UNC name used for network volume. 43 | /// 44 | [field: MarshalAs(UnmanagedType.LPWStr)] 45 | public string? UNCName { get; set; } 46 | 47 | private uint Timeout; 48 | /// 49 | /// Max timeout in milliseconds of each request before Dokan give up. 50 | /// 51 | public System.TimeSpan TimeOut 52 | { 53 | get => System.TimeSpan.FromMilliseconds(Timeout); 54 | set => Timeout = (uint)value.TotalMilliseconds; 55 | } 56 | 57 | /// 58 | /// Allocation Unit Size of the volume. This will behave on the file size. 59 | /// 60 | public int AllocationUnitSize { get; set; } 61 | 62 | /// 63 | /// Sector Size of the volume. This will behave on the file size. 64 | /// 65 | public int SectorSize { get; set; } 66 | /// 67 | /// Length of the optional VolumeSecurityDescriptor provided. Set 0 will disable the option. 68 | /// 69 | public int VolumeSecurityDescriptorLength { get; set; } 70 | 71 | /// 72 | /// Optional Volume Security descriptor. See InitializeSecurityDescriptor 73 | /// 74 | [field: MarshalAs(UnmanagedType.ByValArray, SizeConst = 16384, ArraySubType = UnmanagedType.U1)] 75 | public byte[]? VolumeSecurityDescriptor { get; set; } 76 | } 77 | -------------------------------------------------------------------------------- /DokanNet/Native/SECURITY_INFORMATION.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DokanNet.Native 4 | { 5 | /// 6 | /// Identifies the object-related security information being set or queried. 7 | /// This security information includes: 8 | /// 9 | /// The owner of an object. 10 | /// The primary group of an object. 11 | /// The discretionary access control list(DACL) of an object. 12 | /// The system access control list(SACL) of an object. 13 | /// 14 | /// 15 | /// Structure taken from pinvoke.net 16 | /// \see SECURITY_INFORMATION (MSDN) 17 | [Flags] 18 | internal enum SECURITY_INFORMATION : uint 19 | { 20 | /// 21 | /// The owner identifier of the object is being referenced. 22 | /// 23 | OWNER_SECURITY_INFORMATION = 0x00000001, 24 | 25 | /// 26 | /// The primary group identifier of the object is being referenced. 27 | /// 28 | GROUP_SECURITY_INFORMATION = 0x00000002, 29 | 30 | /// 31 | /// The DACL of the object is being referenced. 32 | /// 33 | DACL_SECURITY_INFORMATION = 0x00000004, 34 | 35 | /// 36 | /// The SACL of the object is being referenced. 37 | /// 38 | SACL_SECURITY_INFORMATION = 0x00000008, 39 | 40 | /// 41 | /// The SACL inherits ACEs from the parent object. 42 | /// 43 | /// Dokan may not be passing Label ?? 0x00000010 44 | UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000, 45 | 46 | /// 47 | /// The DACL inherits ACEs from the parent object. 48 | /// 49 | UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000, 50 | 51 | /// 52 | /// The SACL cannot inherit ACEs. 53 | /// 54 | PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000, 55 | 56 | /// 57 | /// The DACL cannot inherit access control entries (ACEs). 58 | /// 59 | PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 60 | } 61 | } -------------------------------------------------------------------------------- /DokanNet/Native/WIN32_FIND_DATA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; 5 | 6 | namespace DokanNet.Native; 7 | 8 | /// 9 | /// Contains information about the file that is found by the FindFirstFile, FindFirstFileEx, or FindNextFile function. 10 | /// 11 | /// 12 | /// If a file has a long file name, the complete name appears in the cFileName member, and the 8.3 format truncated version 13 | /// of the name appears in the member. Otherwise, is empty. If the FindFirstFileEx function 14 | /// was called with a value of FindExInfoBasic in the fInfoLevelId parameter, the member will always contain 15 | /// a NULL string value. This remains true for all subsequent calls to the FindNextFile function. As an alternative method of 16 | /// retrieving the 8.3 format version of a file name, you can use the GetShortPathName function. For more information about 17 | /// file names, see Naming Files, Paths, and Namespaces (MSDN). 18 | /// 19 | /// Not all file systems can record creation and last access times, and not all file systems record them in the same manner. 20 | /// For example, on the FAT file system, create time has a resolution of 10 milliseconds, write time has a resolution of 21 | /// 2 seconds, and access time has a resolution of 1 day. The NTFS file system delays updates to the last access time for 22 | /// a file by up to 1 hour after the last access.For more information, see File Times (MSDN). 23 | /// 24 | /// \see WIN32_FIND_DATA structure (MSDN) 25 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)] 26 | internal struct WIN32_FIND_DATA 27 | { 28 | /// 29 | /// The file attributes of a file. 30 | /// 31 | /// For possible values and their descriptions, see . 32 | /// The attribute on the file is set if any of 33 | /// the streams of the file have ever been sparse. 34 | /// 35 | public FileAttributes dwFileAttributes; 36 | 37 | /// 38 | /// A structure that specifies when a file or directory was created. 39 | /// If the underlying file system does not support creation time, this member is zero. 40 | /// 41 | public FILETIME ftCreationTime; 42 | 43 | /// 44 | /// A structure. 45 | /// 46 | /// For a file, the structure specifies when the file was last read from, written to, or for executable files, run. 47 | /// 48 | /// For a directory, the structure specifies when the directory is created. 49 | /// If the underlying file system does not support last access time, this member is zero. 50 | /// 51 | /// On the FAT file system, the specified date for both files and directories is correct, 52 | /// but the time of day is always set to midnight. 53 | /// 54 | public FILETIME ftLastAccessTime; 55 | 56 | /// 57 | /// A structure. 58 | /// 59 | /// For a file, the structure specifies when the file was last written to, 60 | /// truncated, or overwritten, for example, when WriteFile or SetEndOfFile 61 | /// are used. The date and time are not updated when file attributes or 62 | /// security descriptors are changed. 63 | /// 64 | /// For a directory, the structure specifies when the directory is created. 65 | /// If the underlying file system does not support last write time, 66 | /// this member is zero. 67 | /// 68 | public FILETIME ftLastWriteTime; 69 | 70 | /// 71 | /// The high-order DWORD value of the file size, in bytes. 72 | /// 73 | /// This value is zero unless the file size is greater than MAXDWORD. 74 | /// 75 | /// The size of the file is equal to (nFileSizeHigh* (MAXDWORD+1)) + nFileSizeLow. 76 | /// 77 | public uint nFileSizeHigh; 78 | 79 | /// 80 | /// The low-order DWORD value of the file size, in bytes. 81 | /// 82 | public uint nFileSizeLow; 83 | 84 | /// 85 | /// If the member includes the attribute, 86 | /// this member specifies the reparse point tag. 87 | /// Otherwise, this value is undefined and should not be used. 88 | /// 89 | /// \see Reparse Point Tags (MSDN) 90 | private readonly uint dwReserved0; 91 | 92 | /// 93 | /// Reserved for future use. 94 | /// 95 | private readonly uint dwReserved1; 96 | 97 | /// 98 | /// The name of the file. 99 | /// 100 | public unsafe fixed char cFileName[260]; 101 | 102 | #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP 103 | public unsafe ReadOnlySpan FileName 104 | { 105 | set => value.CopyTo(MemoryMarshal.CreateSpan(ref cFileName[0], 260)); 106 | } 107 | #else 108 | public unsafe ReadOnlySpan FileName 109 | { 110 | set 111 | { 112 | fixed (char* ptr = cFileName) 113 | { 114 | value.CopyTo(new(ptr, 260)); 115 | } 116 | } 117 | } 118 | #endif 119 | 120 | /// 121 | /// An alternative name for the file. 122 | /// This name is in the classic 8.3 file name format. 123 | /// 124 | public unsafe fixed char cAlternateFileName[14]; 125 | 126 | #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP 127 | public unsafe ReadOnlySpan AlternateFileName 128 | { 129 | set => value.CopyTo(MemoryMarshal.CreateSpan(ref cAlternateFileName[0], 14)); 130 | } 131 | #else 132 | public unsafe ReadOnlySpan AlternateFileName 133 | { 134 | set 135 | { 136 | fixed (char* ptr = cAlternateFileName) 137 | { 138 | value.CopyTo(new(ptr, 14)); 139 | } 140 | } 141 | } 142 | #endif 143 | 144 | /// 145 | /// Obsolete. Do not use. 146 | /// 147 | public uint dwFileType; 148 | 149 | /// 150 | /// Obsolete. Do not use. 151 | /// 152 | public uint dwCreatorType; 153 | 154 | /// 155 | /// Obsolete. Do not use. 156 | /// 157 | public ushort wFinderFlags; 158 | } 159 | -------------------------------------------------------------------------------- /DokanNet/Native/WIN32_FIND_STREAM_DATA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace DokanNet.Native; 5 | 6 | /// 7 | /// Contains information about the stream found by the FindFirstStreamW (MSDN) 8 | /// or FindNextStreamW (MSDN) function. 9 | /// 10 | /// \see WIN32_FIND_STREAM_DATA structure (MSDN) 11 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)] 12 | internal struct WIN32_FIND_STREAM_DATA 13 | { 14 | /// 15 | /// A long value that specifies the size of the stream, in bytes. 16 | /// 17 | public long StreamSize { get; set; } 18 | 19 | /// 20 | /// The name of the stream. The string name format is ":streamname:$streamtype". 21 | /// 22 | public unsafe fixed char cStreamName[260]; 23 | 24 | #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP 25 | public unsafe ReadOnlySpan StreamName 26 | { 27 | set => value.CopyTo(MemoryMarshal.CreateSpan(ref cStreamName[0], 260)); 28 | } 29 | #else 30 | public unsafe ReadOnlySpan StreamName 31 | { 32 | set 33 | { 34 | fixed (char* ptr = cStreamName) 35 | { 36 | value.CopyTo(new(ptr, 260)); 37 | } 38 | } 39 | } 40 | #endif 41 | } 42 | -------------------------------------------------------------------------------- /DokanNet/NullFormatProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DokanNet 4 | { 5 | /// 6 | /// Provide support to format object with null. 7 | /// 8 | public class FormatProviders : IFormatProvider, ICustomFormatter 9 | { 10 | /// 11 | /// A Singleton instance of this class. 12 | /// 13 | public static readonly FormatProviders DefaultFormatProvider = new FormatProviders(); 14 | 15 | /// 16 | /// A constant string that represents what to use if the formated object is null. 17 | /// 18 | public const string NullStringRepresentation = ""; 19 | 20 | /// 21 | /// Prevents a default instance of the class from being created. 22 | /// 23 | private FormatProviders() 24 | { 25 | } 26 | 27 | /// 28 | /// Format a using . 29 | /// 30 | /// The to format. 31 | /// The formated string. 32 | #pragma warning disable 3001 33 | public static string DokanFormat(FormattableString formattable) 34 | => formattable.ToString(DefaultFormatProvider); 35 | #pragma warning restore 3001 36 | 37 | /// 38 | /// Returns an object that provides formatting services for the 39 | /// specified type. 40 | /// 41 | /// An instance of the object specified by 42 | /// , if the 43 | /// implementation can supply 44 | /// that type of object; otherwise, null. 45 | /// An object that specifies the type of format 46 | /// object to return. 47 | public object? GetFormat(Type? formatType) 48 | { 49 | return formatType == typeof(ICustomFormatter) ? this : null; 50 | } 51 | 52 | /// 53 | /// Converts the value of a specified object to an equivalent string 54 | /// representation using specified format and culture-specific 55 | /// formatting information. 56 | /// 57 | /// The string representation of the value of 58 | /// , formatted as specified by 59 | /// and . 60 | /// 61 | /// A format string containing formatting 62 | /// specifications. 63 | /// An object to format. 64 | /// An object that supplies format 65 | /// information about the current instance. 66 | public string Format(string? format, object? arg, IFormatProvider? formatProvider) 67 | { 68 | if (arg == null) 69 | { 70 | return NullStringRepresentation; 71 | } 72 | 73 | var formattable = arg as IFormattable; 74 | 75 | return formattable?.ToString(format, formatProvider) 76 | ?? arg.ToString() ?? ""; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /DokanNet/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | // Make internals visible to tests. 4 | [assembly: InternalsVisibleTo("DokanNet.Tests")] 5 | -------------------------------------------------------------------------------- /DokanNet/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace DokanNet.Properties { 12 | using System; 13 | using System.Reflection; 14 | 15 | 16 | /// 17 | /// A strongly-typed resource class, for looking up localized strings, etc. 18 | /// 19 | // This class was auto-generated by the StronglyTypedResourceBuilder 20 | // class via a tool like ResGen or Visual Studio. 21 | // To add or remove a member, edit your .ResX file then rerun ResGen 22 | // with the /str option, or rebuild your VS project. 23 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 24 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 25 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 26 | internal class Resources { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() { 34 | } 35 | 36 | /// 37 | /// Returns the cached ResourceManager instance used by this class. 38 | /// 39 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 40 | internal static global::System.Resources.ResourceManager ResourceManager { 41 | get { 42 | if (object.ReferenceEquals(resourceMan, null)) { 43 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DokanNet.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); 44 | resourceMan = temp; 45 | } 46 | return resourceMan; 47 | } 48 | } 49 | 50 | /// 51 | /// Overrides the current thread's CurrentUICulture property for all 52 | /// resource lookups using this strongly typed resource class. 53 | /// 54 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 55 | internal static global::System.Globalization.CultureInfo Culture { 56 | get { 57 | return resourceCulture; 58 | } 59 | set { 60 | resourceCulture = value; 61 | } 62 | } 63 | 64 | /// 65 | /// Looks up a localized string similar to Can't assign a drive letter or mount point. 66 | /// 67 | internal static string ErrorAssignDriveLetter { 68 | get { 69 | return ResourceManager.GetString("ErrorAssignDriveLetter", resourceCulture); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized string similar to Bad drive letter. 75 | /// 76 | internal static string ErrorBadDriveLetter { 77 | get { 78 | return ResourceManager.GetString("ErrorBadDriveLetter", resourceCulture); 79 | } 80 | } 81 | 82 | /// 83 | /// Looks up a localized string similar to Dokan error. 84 | /// 85 | internal static string ErrorDokan { 86 | get { 87 | return ResourceManager.GetString("ErrorDokan", resourceCulture); 88 | } 89 | } 90 | 91 | /// 92 | /// Looks up a localized string similar to Can't install the Dokan driver. 93 | /// 94 | internal static string ErrorDriverInstall { 95 | get { 96 | return ResourceManager.GetString("ErrorDriverInstall", resourceCulture); 97 | } 98 | } 99 | 100 | /// 101 | /// Looks up a localized string similar to Mount point is invalid. 102 | /// 103 | internal static string ErrorMountPointInvalid { 104 | get { 105 | return ResourceManager.GetString("ErrorMountPointInvalid", resourceCulture); 106 | } 107 | } 108 | 109 | /// 110 | /// Looks up a localized string similar to Something's wrong with the Dokan driver. 111 | /// 112 | internal static string ErrorStart { 113 | get { 114 | return ResourceManager.GetString("ErrorStart", resourceCulture); 115 | } 116 | } 117 | 118 | /// 119 | /// Looks up a localized string similar to Unknown error. 120 | /// 121 | internal static string ErrorUnknown { 122 | get { 123 | return ResourceManager.GetString("ErrorUnknown", resourceCulture); 124 | } 125 | } 126 | 127 | /// 128 | /// Looks up a localized string similar to Version error. 129 | /// 130 | internal static string ErrorVersion { 131 | get { 132 | return ResourceManager.GetString("ErrorVersion", resourceCulture); 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /DokanNet/Properties/Resources.de.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Ungültiger Laufwerksbuchstabe 122 | 123 | 124 | Der Dokan Driver kann nicht installiert werden 125 | 126 | 127 | Das Laufwerk bzw. die Verknüpfung kann nicht zugewiesen werden. 128 | 129 | 130 | Es ist ein Fehler im Dokan Treiber aufgetreten 131 | 132 | 133 | Die Verknüpfung ist ungültig 134 | 135 | 136 | Version Fehler 137 | 138 | 139 | Dokan Fehler 140 | 141 | -------------------------------------------------------------------------------- /DokanNet/Properties/Resources.fr.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | text/microsoft-resx 90 | 91 | 92 | 1.3 93 | 94 | 95 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 96 | 97 | 98 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 99 | 100 | 101 | Mauvaise lettre de lecteur 102 | 103 | 104 | Impossible d'installer le driver Dokan 105 | 106 | 107 | Impossible d'assigner la lettre ou le point de montage 108 | 109 | 110 | Il y a eu une erreur dans le driver Dokan 111 | 112 | 113 | Le point de montage est invalide 114 | 115 | 116 | Erreur de version 117 | 118 | 119 | Erreur Dokan 120 | 121 | -------------------------------------------------------------------------------- /DokanNet/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Bad drive letter 122 | 123 | 124 | Can't install the Dokan driver 125 | 126 | 127 | Can't assign a drive letter or mount point 128 | 129 | 130 | Something's wrong with the Dokan driver 131 | 132 | 133 | Mount point is invalid 134 | 135 | 136 | Version error 137 | 138 | 139 | Dokan error 140 | 141 | 142 | Unknown error 143 | 144 | -------------------------------------------------------------------------------- /DokanNet/Properties/Resources.sv.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Felaktig enhetsbokstav 122 | 123 | 124 | Kan inte installera Dokan-drivrutinen 125 | 126 | 127 | Kan inte tilldela en enhetsbokstav eller monteringspunkt 128 | 129 | 130 | Något är fel med Dokan-drivrutinen 131 | 132 | 133 | Monteringspunkt är felaktig 134 | 135 | 136 | Versionsfel 137 | 138 | 139 | Dokan-fel 140 | 141 | 142 | Okänt fel 143 | 144 | -------------------------------------------------------------------------------- /DokanNet/documentations/mainpage.md: -------------------------------------------------------------------------------- 1 | Dokan-dotnet Doxygen {#mainpage} 2 | ================================================== 3 | 4 | This is the homepage of the Doxygen generated documentation for 5 | Dokan-dotnet. 6 | 7 | Dokan-dotnet is a wrapper for Dokan User mode file system library. 8 | 9 | We recommend you take a look at [our wiki](https://github.com/dokan-dev/dokany/wiki) for 10 | an introduction of how to use Dokan. 11 | You may find Mirror and RegistryFS samples [here](https://github.com/dokan-dev/dokan-dotnet/tree/master/sample) useful. 12 | -------------------------------------------------------------------------------- /DokanNet/documentations/resources/customdoxygen.css: -------------------------------------------------------------------------------- 1 | h1, .h1, h2, .h2, h3, .h3{ 2 | font-weight: 200 !important; 3 | } 4 | 5 | #navrow1, #navrow2, #navrow3, #navrow4, #navrow5{ 6 | border-bottom: 1px solid #EEEEEE; 7 | } 8 | 9 | .adjust-right { 10 | margin-left: 30px !important; 11 | font-size: 1.15em !important; 12 | } 13 | .navbar{ 14 | border: 0px solid #222 !important; 15 | } 16 | table{ 17 | white-space:pre-wrap !important; 18 | } 19 | /* 20 | =========================== 21 | */ 22 | 23 | 24 | /* Sticky footer styles 25 | -------------------------------------------------- */ 26 | html, 27 | body { 28 | height: 100%; 29 | /* The html and body elements cannot have any padding or margin. */ 30 | } 31 | 32 | /* Wrapper for page content to push down footer */ 33 | #wrap { 34 | min-height: 100%; 35 | height: auto; 36 | /* Negative indent footer by its height */ 37 | margin: 0 auto -60px; 38 | /* Pad bottom by footer height */ 39 | padding: 0 0 60px; 40 | } 41 | 42 | /* Set the fixed height of the footer here */ 43 | #footer { 44 | font-size: 0.9em; 45 | padding: 8px 0px; 46 | background-color: #f5f5f5; 47 | } 48 | 49 | .footer-row { 50 | line-height: 44px; 51 | } 52 | 53 | #footer > .container { 54 | padding-left: 15px; 55 | padding-right: 15px; 56 | } 57 | 58 | .footer-follow-icon { 59 | margin-left: 3px; 60 | text-decoration: none !important; 61 | } 62 | 63 | .footer-follow-icon img { 64 | width: 20px; 65 | } 66 | 67 | .footer-link { 68 | padding-top: 5px; 69 | display: inline-block; 70 | color: #999999; 71 | text-decoration: none; 72 | } 73 | 74 | .footer-copyright { 75 | text-align: center; 76 | } 77 | 78 | 79 | @media (min-width: 992px) { 80 | .footer-row { 81 | text-align: left; 82 | } 83 | 84 | .footer-icons { 85 | text-align: right; 86 | } 87 | } 88 | @media (max-width: 991px) { 89 | .footer-row { 90 | text-align: center; 91 | } 92 | 93 | .footer-icons { 94 | text-align: center; 95 | } 96 | } 97 | 98 | /* DOXYGEN Code Styles 99 | ----------------------------------- */ 100 | 101 | 102 | a.qindex { 103 | font-weight: bold; 104 | } 105 | 106 | a.qindexHL { 107 | font-weight: bold; 108 | background-color: #9CAFD4; 109 | color: #ffffff; 110 | border: 1px double #869DCA; 111 | } 112 | 113 | .contents a.qindexHL:visited { 114 | color: #ffffff; 115 | } 116 | 117 | a.code, a.code:visited, a.line, a.line:visited { 118 | color: #4665A2; 119 | } 120 | 121 | a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { 122 | color: #4665A2; 123 | } 124 | 125 | /* @end */ 126 | 127 | dl.el { 128 | margin-left: -1cm; 129 | } 130 | 131 | pre.fragment { 132 | border: 1px solid #C4CFE5; 133 | background-color: #FBFCFD; 134 | padding: 4px 6px; 135 | margin: 4px 8px 4px 2px; 136 | overflow: auto; 137 | word-wrap: break-word; 138 | font-size: 9pt; 139 | line-height: 125%; 140 | font-family: monospace, fixed; 141 | font-size: 105%; 142 | } 143 | 144 | div.fragment { 145 | padding: 4px 6px; 146 | margin: 4px 8px 4px 2px; 147 | border: 1px solid #C4CFE5; 148 | } 149 | 150 | div.line { 151 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 152 | font-size: 12px; 153 | min-height: 13px; 154 | line-height: 1.0; 155 | text-wrap: unrestricted; 156 | white-space: -moz-pre-wrap; /* Moz */ 157 | white-space: -pre-wrap; /* Opera 4-6 */ 158 | white-space: -o-pre-wrap; /* Opera 7 */ 159 | white-space: pre-wrap; /* CSS3 */ 160 | word-wrap: normal; /* IE 5.5+ */ 161 | text-indent: -53px; 162 | padding-left: 53px; 163 | padding-bottom: 0px; 164 | margin: 0px; 165 | -webkit-transition-property: background-color, box-shadow; 166 | -webkit-transition-duration: 0.5s; 167 | -moz-transition-property: background-color, box-shadow; 168 | -moz-transition-duration: 0.5s; 169 | -ms-transition-property: background-color, box-shadow; 170 | -ms-transition-duration: 0.5s; 171 | -o-transition-property: background-color, box-shadow; 172 | -o-transition-duration: 0.5s; 173 | transition-property: background-color, box-shadow; 174 | transition-duration: 0.5s; 175 | } 176 | div.line:hover{ 177 | background-color: #FBFF00; 178 | } 179 | 180 | div.line.glow { 181 | background-color: cyan; 182 | box-shadow: 0 0 10px cyan; 183 | } 184 | 185 | 186 | span.lineno { 187 | padding-right: 4px; 188 | text-align: right; 189 | color:rgba(0,0,0,0.3); 190 | border-right: 1px solid #EEE; 191 | border-left: 1px solid #EEE; 192 | background-color: #FFF; 193 | white-space: pre; 194 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace ; 195 | } 196 | span.lineno a { 197 | background-color: #FAFAFA; 198 | cursor:pointer; 199 | } 200 | 201 | span.lineno a:hover { 202 | background-color: #EFE200; 203 | color: #1e1e1e; 204 | } 205 | 206 | div.groupHeader { 207 | margin-left: 16px; 208 | margin-top: 12px; 209 | font-weight: bold; 210 | } 211 | 212 | div.groupText { 213 | margin-left: 16px; 214 | font-style: italic; 215 | } 216 | 217 | /* @group Code Colorization */ 218 | 219 | span.keyword { 220 | color: #008000 221 | } 222 | 223 | span.keywordtype { 224 | color: #604020 225 | } 226 | 227 | span.keywordflow { 228 | color: #e08000 229 | } 230 | 231 | span.comment { 232 | color: #800000 233 | } 234 | 235 | span.preprocessor { 236 | color: #806020 237 | } 238 | 239 | span.stringliteral { 240 | color: #002080 241 | } 242 | 243 | span.charliteral { 244 | color: #008080 245 | } 246 | 247 | span.vhdldigit { 248 | color: #ff00ff 249 | } 250 | 251 | span.vhdlchar { 252 | color: #000000 253 | } 254 | 255 | span.vhdlkeyword { 256 | color: #700070 257 | } 258 | 259 | span.vhdllogic { 260 | color: #ff0000 261 | } 262 | 263 | blockquote { 264 | background-color: #F7F8FB; 265 | border-left: 2px solid #9CAFD4; 266 | margin: 0 24px 0 4px; 267 | padding: 0 12px 0 16px; 268 | } 269 | 270 | /*---------------- Search Box */ 271 | 272 | #search-box { 273 | margin: 10px 0px; 274 | } 275 | #search-box .close { 276 | display: none; 277 | position: absolute; 278 | right: 0px; 279 | padding: 6px 12px; 280 | z-index: 5; 281 | } 282 | 283 | /*---------------- Search results window */ 284 | 285 | #search-results-window { 286 | display: none; 287 | } 288 | 289 | iframe#MSearchResults { 290 | width: 100%; 291 | height: 15em; 292 | } 293 | 294 | .SRChildren { 295 | padding-left: 3ex; padding-bottom: .5em 296 | } 297 | .SRPage .SRChildren { 298 | display: none; 299 | } 300 | a.SRScope { 301 | display: block; 302 | } 303 | a.SRSymbol:focus, a.SRSymbol:active, 304 | a.SRScope:focus, a.SRScope:active { 305 | text-decoration: underline; 306 | } 307 | span.SRScope { 308 | padding-left: 4px; 309 | } 310 | .SRResult { 311 | display: none; 312 | } 313 | 314 | /* class and file list */ 315 | .directory .icona, 316 | .directory .arrow { 317 | height: auto; 318 | } 319 | .directory .icona .icon { 320 | height: 16px; 321 | } 322 | .directory .icondoc { 323 | background-position: 0px 0px; 324 | height: 20px; 325 | } 326 | .directory .iconfopen { 327 | background-position: 0px 0px; 328 | } 329 | .directory td.entry { 330 | padding: 7px 8px 6px 8px; 331 | } 332 | 333 | .table > tbody > tr > td.memSeparator { 334 | line-height: 0; 335 | .table-hover; 336 | 337 | } 338 | 339 | .memItemLeft, .memTemplItemLeft { 340 | white-space: normal; 341 | } 342 | 343 | /* enumerations */ 344 | .panel-body thead > tr { 345 | background-color: #e0e0e0; 346 | } 347 | 348 | /* todo lists */ 349 | .todoname, 350 | .todoname a { 351 | font-weight: bold; 352 | } 353 | 354 | /* Class title */ 355 | .summary { 356 | margin-top: 25px; 357 | } 358 | .page-header { 359 | margin: 20px 0px !important; 360 | } 361 | .page-header .title { 362 | display: inline-block; 363 | } 364 | .page-header .pull-right { 365 | margin-top: 0.3em; 366 | margin-left: 0.5em; 367 | } 368 | .page-header .label { 369 | font-size: 50%; 370 | } 371 | -------------------------------------------------------------------------------- /DokanNet/documentations/resources/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /DokanNet/documentations/resources/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | $projectname: $title 15 | $title 16 | 17 | 18 | $treeview 19 | $search 20 | $mathjax 21 | 22 | $extrastylesheet 23 | 24 | 25 | 26 | 27 | 28 | 29 | 36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dokan.NET Binding 2 | [![Build status](https://ci.appveyor.com/api/projects/status/w707j7xlu21jf3qa?svg=true)](https://ci.appveyor.com/project/Liryna/dokan-dotnet) 3 | [![NuGet downloads](https://img.shields.io/nuget/dt/DokanNet.svg)](https://www.nuget.org/packages/DokanNet) 4 | [![Version](https://img.shields.io/nuget/v/DokanNet.svg)](https://www.nuget.org/packages/DokanNet) 5 | 6 | ## What is Dokan.NET Binding 7 | By using Dokan library, you can create your own file systems very easily 8 | without writing device driver. Dokan.NET Binding is a library that allows 9 | you to make a file system on .NET environment. 10 | 11 | ## Install 12 | 13 | To install DokanNet, run the following command in the Package Manager Console 14 | 15 | PM> Install-Package DokanNet 16 | 17 | //Prerelease 18 | PM> Install-Package DokanNet -Pre 19 | 20 | ## Licensing 21 | Dokan.NET Binding is distributed under a version of the "MIT License", 22 | which is a BSD-like license. See the 'license.mit.txt' file for details. 23 | 24 | ## Environment 25 | * Either of Microsoft .NET Framework 4.6, .NET Framework 4.8, .NET Standard 2.0, .NET Standard 2.1, .NET 8.0 or .NET 9.0 26 | * Dokan library 27 | 28 | ## How to write a file system 29 | To make a file system, an application needs to implement IDokanOperations interface, or the modernized variant, IDokanOperations2. 30 | Once implemented, you can invoke Mount function on your driver instance 31 | to mount a drive. The function blocks until the file system is unmounted. 32 | Semantics and parameters are just like Dokan library. Details are described 33 | at `README.md` file in Dokan library. See sample codes under 'sample' 34 | directory. 35 | Doxygen documentation is also available [![API documentation](https://img.shields.io/badge/Documentation-API-green.svg)](https://dokan-dev.github.io/dokan-dotnet-doc/html/) 36 | 37 | ## Unmounting 38 | Just run the bellow command or your file system application call Dokan.Unmount 39 | to unmount a drive. 40 | 41 | > dokanctl.exe /u DriveLetter 42 | 43 | -------------------------------------------------------------------------------- /UpgradeLog.htm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dokan-dev/dokan-dotnet/7271b35414a31b2607c4abe59db9782bc2d6a572/UpgradeLog.htm -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | AccessTokenDokanDoc: 3 | secure: DN88uTxiO/keexbCfFg47FGQvXG3BaxWeYJvZftxGQ7tHLrBPnAqjZe4LmwuofFa 4 | global: 5 | DOKAN_CI_CACHE: C:\dokan_ci_cache 6 | DOXYGEN_INST_DIR: '%DOKAN_CI_CACHE%\doxygen' 7 | 8 | version: 2.3.0-{build} 9 | configuration: 10 | - Release 11 | - Debug 12 | os: Visual Studio 2022 13 | 14 | # To debug build issues, add your own fork to AppVeyor and uncomment below. 15 | # Connection details will be printed to the console output. 16 | # $blockRdp makes the build block until a file is deleted from the desktop. 17 | #init: 18 | #- ps: Invoke-Expression (Invoke-WebRequest 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1') 19 | #on_finish: 20 | #- ps: $blockRdp = $true; Invoke-Expression (Invoke-WebRequest 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1') 21 | 22 | install: 23 | - ps: | 24 | function Exec-External { 25 | param( 26 | [Parameter(Position=0,Mandatory=1)][scriptblock] $command 27 | ) 28 | & $command 29 | if ($LASTEXITCODE -ne 0) { 30 | throw ("Command returned non-zero error-code ${LASTEXITCODE}: $command") 31 | } 32 | } 33 | - ps: | 34 | Write-Host "Dokan download..." -ForegroundColor Green 35 | New-item -type directory -path C:\projects\dokan-dotnet\ -Force 36 | Start-FileDownload 'https://github.com/dokan-dev/dokany/releases/download/v2.3.0.1000/DokanSetup.exe' -FileName 'C:\projects\dokan-dotnet\DokanSetup.exe' 37 | Write-Host "Dokan downloaded." -ForegroundColor Green 38 | - cmd: | 39 | C:\projects\dokan-dotnet\DokanSetup.exe /quiet 40 | 41 | before_build: 42 | - nuget restore 43 | build: 44 | project: DokanNet.sln 45 | verbosity: minimal 46 | after_build: 47 | - ps: | 48 | #Stop the mirror process and wait for the driver to be unmounted. 49 | Function Stop-Mirror { 50 | param( 51 | [Parameter(Position=0,Mandatory=1)][System.ComponentModel.Component] $app 52 | ) 53 | Stop-Process -Id $app.Id 54 | Wait-Process -Id $app.Id -EA SilentlyContinue 55 | 56 | $countdown = 1 57 | while ((Test-Path 'N:\')) 58 | { 59 | Write-output "Waiting for unmount and Counting down $Countdown" 60 | Start-Sleep -Seconds 1 61 | if($countdown++ -ge 5) { 62 | Throw "It took more than 5 seconds to unmount" 63 | } 64 | } 65 | } 66 | - ps: | 67 | git clone -q https://github.com/Liryna/winfstest.git 68 | $buildCmd = "MSBuild.exe" 69 | $buildArgs = @( 70 | ".\winfstest\winfstest.sln", 71 | "/m", 72 | "/l:C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll", 73 | "/p:Configuration=Debug", 74 | "/p:Platform=x64") 75 | Exec-External { & $buildCmd $buildArgs } 76 | New-Item C:\TMP -type directory 77 | ls c:\TMP 78 | 79 | foreach ($target in @("net48")) { 80 | if ($app) { 81 | Write-Host "Waiting for the driver to unmount..." 82 | Stop-Mirror($app) 83 | } 84 | Write-Host "Run Windows file system testing for $target..." -ForegroundColor Green 85 | $app = Start-Process -passthru .\sample\DokanNetMirror\bin\$env:CONFIGURATION\$target\DokanNetMirror.exe 86 | Start-Sleep -s 5 87 | 88 | Exec-External { & .\winfstest\TestSuite\run-winfstest.bat t\base N:\TMP\ } 89 | Start-Sleep -s 5 90 | } 91 | #Stop the process from the last iteration, but do not wait for the driver to unmount. 92 | Stop-Process -Id $app.Id 93 | test: 94 | categories: 95 | except: 96 | - Manual 97 | - Timing 98 | 99 | on_success: 100 | - ps: | 101 | if ($env:CONFIGURATION -eq "Release") { 102 | if ("$env:APPVEYOR_PULL_REQUEST_TITLE" -or "$env:APPVEYOR_REPO_BRANCH" -ne "master") { 103 | return; 104 | } 105 | 106 | $env:PATH = "$env:DOXYGEN_INST_DIR;$env:PATH" 107 | if (Test-Path $env:DOXYGEN_INST_DIR) { 108 | Write-Host "Doxygen in cache, skipping." 109 | return 110 | } 111 | New-Item -Force -Type Directory $env:DOXYGEN_INST_DIR | Out-Null 112 | cd $env:DOXYGEN_INST_DIR 113 | $doxygen_zip = "$env:TEMP\doxygen.zip" 114 | # Version has to be < 1.8.12 because doxyboot theme is incompatible with later versions 115 | # https://github.com/Velron/doxygen-bootstrapped/issues/20 116 | # https://github.com/Velron/doxygen-bootstrapped/issues/27 117 | Write-Host "Downloading Doxygen..." 118 | Invoke-WebRequest https://netcologne.dl.sourceforge.net/project/doxygen/rel-1.8.11/doxygen-1.8.11.windows.x64.bin.zip -OutFile $doxygen_zip -UserAgent [Microsoft.PowerShell.Commands.PSUserAgent]::FireFox 119 | if ($(Get-FileHash -Algorithm SHA256 $doxygen_zip).Hash -ne "6CE7C259975FB3DC449313913DE71B89665D15C49CF674DB6952F304BB3DFAAA") { 120 | throw "Hash mismatch while downloading Doxygen" 121 | } 122 | Exec-External { & 7z x -y "$doxygen_zip" } 123 | 124 | # Update documentation 125 | cd $env:APPVEYOR_BUILD_FOLDER\DokanNet\documentations 126 | git config --global user.email "appveyor@appveyor.org" 127 | git config --global user.name "appveyor" 128 | git.exe clone -b gh-pages --single-branch https://lirynastark:$($env:AccessTokenDokanDoc)@github.com/dokan-dev/dokan-dotnet-doc.git doc 129 | Exec-External { & doxygen.exe Doxyfile } 130 | cd doc 131 | if ($(git status --porcelain)) { 132 | Write-Host "Update documentation..." -ForegroundColor Green 133 | git add -A 134 | Write-Host "All changes ready to commit" -ForegroundColor Green 135 | git commit -m "Latest documentation on successful appveyor build $env:APPVEYOR_BUILD_VERSION auto-pushed to gh-pages" 136 | Write-Host "Push changes..." -ForegroundColor Green 137 | git push -f origin gh-pages 138 | Write-Host "Pushed. Documentation updated!" -ForegroundColor Green 139 | } else { 140 | Write-Host "No documentation changes detected." -ForegroundColor Green 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /dokan_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dokan-dev/dokan-dotnet/7271b35414a31b2607c4abe59db9782bc2d6a572/dokan_logo.png -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 - 2019 Adrien J. and Maxime C. 2 | Copyright (c) 2007 Hiroki Asakawa 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /sample/DokanNetMirror/DokanNetMirror.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0;net46;net48 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | MinimumRecommendedRules.ruleset 18 | 19 | false 20 | False 21 | 22 | -------------------------------------------------------------------------------- /sample/DokanNetMirror/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Microsoft.Win32.SafeHandles; 4 | 5 | namespace DokanNetMirror; 6 | 7 | public static class Extensions 8 | { 9 | /// returns the new position of the file pointer 10 | /// 11 | /// 12 | public static long SetFilePointer(this SafeFileHandle fileHandle, long offset, System.IO.SeekOrigin seekOrigin = System.IO.SeekOrigin.Begin) 13 | { 14 | if (fileHandle.IsInvalid) 15 | { 16 | throw new InvalidOperationException("can't set pointer on an invalid handle"); 17 | } 18 | 19 | var wasSet = NativeMethods.SetFilePointerEx(fileHandle, offset, out var newposition, seekOrigin); 20 | if (!wasSet) 21 | { 22 | throw new Win32Exception(); 23 | } 24 | 25 | return newposition; 26 | } 27 | 28 | public static void ReadFile(this SafeFileHandle fileHandle, IntPtr buffer, uint bytesToRead, out int bytesRead) 29 | { 30 | if (fileHandle.IsInvalid) 31 | { 32 | throw new InvalidOperationException("can't set pointer on an invalid handle"); 33 | } 34 | 35 | if (!NativeMethods.ReadFile(fileHandle, buffer, bytesToRead, out bytesRead, IntPtr.Zero)) 36 | { 37 | throw new Win32Exception(); 38 | } 39 | } 40 | 41 | public static void WriteFile(this SafeFileHandle fileHandle, IntPtr buffer, uint bytesToWrite, out int bytesWritten) 42 | { 43 | if (fileHandle.IsInvalid) 44 | { 45 | throw new InvalidOperationException("can't set pointer on an invalid handle"); 46 | } 47 | 48 | if (!NativeMethods.WriteFile(fileHandle, buffer, bytesToWrite, out bytesWritten, IntPtr.Zero)) 49 | { 50 | throw new Win32Exception(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sample/DokanNetMirror/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.InteropServices; 4 | using Microsoft.Win32.SafeHandles; 5 | 6 | namespace DokanNetMirror; 7 | 8 | internal static class NativeMethods 9 | { 10 | /// 11 | /// Sets the date and time that the specified file or directory was created, last accessed, or last modified. 12 | /// 13 | /// A to the file or directory. 14 | /// To get the handler, can be used. 15 | /// A Windows File Time that contains the new creation date and time 16 | /// for the file or directory. 17 | /// If the application does not need to change this information, set this parameter to 0. 18 | /// A Windows File Time that contains the new last access date and time 19 | /// for the file or directory. The last access time includes the last time the file or directory 20 | /// was written to, read from, or (in the case of executable files) run. 21 | /// If the application does not need to change this information, set this parameter to 0. 22 | /// A Windows File Time that contains the new last modified date and time 23 | /// for the file or directory. If the application does not need to change this information, 24 | /// set this parameter to 0. 25 | /// If the function succeeds, the return value is true. 26 | /// \see SetFileTime function (MSDN) 27 | [DllImport("kernel32", SetLastError = true)] 28 | public static extern bool SetFileTime(SafeFileHandle hFile, ref long lpCreationTime, ref long lpLastAccessTime, ref long lpLastWriteTime); 29 | 30 | [DllImport("kernel32.dll", SetLastError = true)] 31 | public static extern bool SetFilePointerEx(SafeFileHandle hFile, long liDistanceToMove, out long lpNewFilePointer, [MarshalAs(UnmanagedType.U4)] System.IO.SeekOrigin dwMoveMethod); 32 | 33 | [DllImport("kernel32.dll", SetLastError = true)] 34 | public static extern bool ReadFile(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out int lpNumberOfBytesRead, IntPtr lpOverlapped); 35 | 36 | [DllImport("kernel32.dll", SetLastError = true)] 37 | public static extern bool WriteFile(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToWrite, out int lpNumberOfBytesWritten, IntPtr lpOverlapped); 38 | } -------------------------------------------------------------------------------- /sample/DokanNetMirror/Notify.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.Versioning; 4 | using DokanNet; 5 | 6 | namespace DokanNetMirror; 7 | 8 | #if NET5_0_OR_GREATER 9 | [SupportedOSPlatform("windows")] 10 | #endif 11 | internal class Notify : IDisposable 12 | { 13 | private readonly string _sourcePath; 14 | private readonly string _targetPath; 15 | private readonly DokanInstance _dokanInstance; 16 | private readonly FileSystemWatcher _commonFsWatcher; 17 | private readonly FileSystemWatcher _fileFsWatcher; 18 | private readonly FileSystemWatcher _dirFsWatcher; 19 | private bool _disposed; 20 | 21 | public Notify(string mirrorPath, string mountPath, DokanInstance dokanInstance) 22 | { 23 | _sourcePath = mirrorPath; 24 | _targetPath = mountPath; 25 | _dokanInstance = dokanInstance; 26 | 27 | _commonFsWatcher = new FileSystemWatcher(mirrorPath) 28 | { 29 | IncludeSubdirectories = true, 30 | NotifyFilter = NotifyFilters.Attributes | 31 | NotifyFilters.CreationTime | 32 | NotifyFilters.DirectoryName | 33 | NotifyFilters.FileName | 34 | NotifyFilters.LastAccess | 35 | NotifyFilters.LastWrite | 36 | NotifyFilters.Security | 37 | NotifyFilters.Size 38 | }; 39 | 40 | _commonFsWatcher.Changed += OnCommonFileSystemWatcherChanged; 41 | _commonFsWatcher.Created += OnCommonFileSystemWatcherCreated; 42 | _commonFsWatcher.Renamed += OnCommonFileSystemWatcherRenamed; 43 | 44 | _commonFsWatcher.EnableRaisingEvents = true; 45 | 46 | _fileFsWatcher = new FileSystemWatcher(mirrorPath) 47 | { 48 | IncludeSubdirectories = true, 49 | NotifyFilter = NotifyFilters.FileName 50 | }; 51 | 52 | _fileFsWatcher.Deleted += OnCommonFileSystemWatcherFileDeleted; 53 | 54 | _fileFsWatcher.EnableRaisingEvents = true; 55 | 56 | _dirFsWatcher = new FileSystemWatcher(mirrorPath) 57 | { 58 | IncludeSubdirectories = true, 59 | NotifyFilter = NotifyFilters.DirectoryName 60 | }; 61 | 62 | _dirFsWatcher.Deleted += OnCommonFileSystemWatcherDirectoryDeleted; 63 | 64 | _dirFsWatcher.EnableRaisingEvents = true; 65 | } 66 | 67 | private string AlterPathToMountPath(string path) 68 | { 69 | var relativeMirrorPath = path.Substring(_sourcePath.Length).TrimStart('\\'); 70 | 71 | return Path.Combine(_targetPath, relativeMirrorPath); 72 | } 73 | 74 | private void OnCommonFileSystemWatcherFileDeleted(object sender, FileSystemEventArgs e) 75 | { 76 | if (_dokanInstance.IsDisposed) 77 | { 78 | return; 79 | } 80 | 81 | var fullPath = AlterPathToMountPath(e.FullPath); 82 | 83 | _dokanInstance.NotifyDelete(fullPath, false); 84 | } 85 | 86 | private void OnCommonFileSystemWatcherDirectoryDeleted(object sender, FileSystemEventArgs e) 87 | { 88 | if (_dokanInstance.IsDisposed) 89 | { 90 | return; 91 | } 92 | 93 | var fullPath = AlterPathToMountPath(e.FullPath); 94 | 95 | _dokanInstance.NotifyDelete(fullPath, true); 96 | } 97 | 98 | private void OnCommonFileSystemWatcherChanged(object sender, FileSystemEventArgs e) 99 | { 100 | if (_dokanInstance.IsDisposed) 101 | { 102 | return; 103 | } 104 | 105 | var fullPath = AlterPathToMountPath(e.FullPath); 106 | 107 | _dokanInstance.NotifyUpdate(fullPath); 108 | } 109 | 110 | private void OnCommonFileSystemWatcherCreated(object sender, FileSystemEventArgs e) 111 | { 112 | if (_dokanInstance.IsDisposed) 113 | { 114 | return; 115 | } 116 | 117 | var fullPath = AlterPathToMountPath(e.FullPath); 118 | var isDirectory = Directory.Exists(fullPath); 119 | 120 | _dokanInstance.NotifyCreate(fullPath, isDirectory); 121 | } 122 | 123 | private void OnCommonFileSystemWatcherRenamed(object sender, RenamedEventArgs e) 124 | { 125 | if (_dokanInstance.IsDisposed) 126 | { 127 | return; 128 | } 129 | 130 | var oldFullPath = AlterPathToMountPath(e.OldFullPath); 131 | var oldDirectoryName = Path.GetDirectoryName(e.OldFullPath); 132 | 133 | var fullPath = AlterPathToMountPath(e.FullPath); 134 | var directoryName = Path.GetDirectoryName(e.FullPath); 135 | 136 | var isDirectory = Directory.Exists(e.FullPath); 137 | var isInSameDirectory = string.Equals(oldDirectoryName, directoryName); 138 | 139 | _dokanInstance.NotifyRename(oldFullPath, fullPath, isDirectory, isInSameDirectory); 140 | } 141 | 142 | protected virtual void Dispose(bool disposing) 143 | { 144 | if (!_disposed) 145 | { 146 | if (disposing) 147 | { 148 | // dispose managed state (managed objects) 149 | _commonFsWatcher.Changed -= OnCommonFileSystemWatcherChanged; 150 | _commonFsWatcher.Created -= OnCommonFileSystemWatcherCreated; 151 | _commonFsWatcher.Renamed -= OnCommonFileSystemWatcherRenamed; 152 | 153 | _commonFsWatcher.Dispose(); 154 | _fileFsWatcher.Dispose(); 155 | _dirFsWatcher.Dispose(); 156 | 157 | } 158 | 159 | // free unmanaged resources (unmanaged objects) and override finalizer 160 | // set large fields to null 161 | _disposed = true; 162 | } 163 | } 164 | 165 | public void Dispose() 166 | { 167 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 168 | Dispose(disposing: true); 169 | GC.SuppressFinalize(this); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /sample/DokanNetMirror/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Runtime.InteropServices; 4 | using System.Runtime.Versioning; 5 | using System.Threading.Tasks; 6 | using DokanNet; 7 | using DokanNet.Logging; 8 | 9 | namespace DokanNetMirror; 10 | 11 | internal class Program 12 | { 13 | private const string MirrorKey = "-what"; 14 | private const string MountKey = "-where"; 15 | private const string UseUnsafeKey = "-unsafe"; 16 | 17 | private static async Task Main(string[] args) 18 | { 19 | try 20 | { 21 | #if NETCOREAPP || NET471_OR_GREATER 22 | if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 23 | { 24 | Console.WriteLine("Only supported on Windows."); 25 | return; 26 | } 27 | #endif 28 | 29 | var arguments = args 30 | .Select(x => x.Split(['='], 2, StringSplitOptions.RemoveEmptyEntries)) 31 | .ToDictionary(x => x[0], x => x.Length > 1 ? x[1] as object : true, StringComparer.OrdinalIgnoreCase); 32 | 33 | var mirrorPath = arguments.TryGetValue(MirrorKey, out var mirrorPathObj) 34 | ? (string)mirrorPathObj : @"C:\"; 35 | 36 | var mountPath = arguments.TryGetValue(MountKey, out var mountPathObj) 37 | ? (string)mountPathObj : @"N:\"; 38 | 39 | var unsafeReadWrite = arguments.ContainsKey(UseUnsafeKey); 40 | 41 | using (var mirrorLogger = new ConsoleLogger("[Mirror] ")) 42 | using (var dokanLogger = new ConsoleLogger("[Dokan] ")) 43 | using (var dokan = new DokanNet.Dokan(dokanLogger)) 44 | { 45 | Console.WriteLine($"Using unsafe methods: {unsafeReadWrite}"); 46 | var mirror = unsafeReadWrite 47 | ? new UnsafeMirror(mirrorLogger, mirrorPath) 48 | : new Mirror(mirrorLogger, mirrorPath); 49 | 50 | var dokanBuilder = new DokanInstanceBuilder(dokan) 51 | .ConfigureLogger(() => dokanLogger) 52 | .ConfigureOptions(options => 53 | { 54 | options.Options = DokanNet.DokanOptions.DebugMode | DokanNet.DokanOptions.EnableNotificationAPI; 55 | options.MountPoint = mountPath; 56 | }); 57 | 58 | using var dokanInstance = dokanBuilder.Build(mirror); 59 | 60 | using var notify = new Notify(mirrorPath, mountPath, dokanInstance); 61 | 62 | Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) => 63 | { 64 | e.Cancel = true; 65 | 66 | #if NETCOREAPP || NET471_OR_GREATER 67 | if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 68 | { 69 | Console.WriteLine("Only supported on Windows."); 70 | return; 71 | } 72 | #endif 73 | 74 | dokan.RemoveMountPoint(mountPath); 75 | }; 76 | 77 | await dokanInstance.WaitForFileSystemClosedAsync(uint.MaxValue); 78 | } 79 | 80 | Console.WriteLine(@"Success"); 81 | } 82 | catch (DokanNet.DokanException ex) 83 | { 84 | Console.WriteLine(@"Error: " + ex.Message); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /sample/DokanNetMirror/UnsafeMirror.cs: -------------------------------------------------------------------------------- 1 | /* 2 | There is various way we could have implemented this, for example we could have used the Filestream.Seek to move the 3 | filesystem pointer, but for didactic reasons we want to use as much pInvoke as possible. 4 | Also in .NET6 any call through the stream seems to move the pointer back to the position memorized by the stream 5 | https://github.com/dotnet/docs/blob/12473973716efbf21470ecbc96de0bd5e1f65e3b/docs/core/compatibility/core-libraries/6.0/filestream-doesnt-sync-offset-with-os.md 6 | hence we want to avoid touching the filestream once obtained the safefilehandle 7 | */ 8 | 9 | using System; 10 | using System.Globalization; 11 | using System.IO; 12 | using System.Runtime.Versioning; 13 | using DokanNet; 14 | using DokanNet.Logging; 15 | using Microsoft.Win32.SafeHandles; 16 | using LTRData.Extensions.Native.Memory; 17 | using FileAccess = System.IO.FileAccess; 18 | 19 | namespace DokanNetMirror; 20 | 21 | /// 22 | /// Implementation of IDokanOperationsUnsafe to demonstrate usage. 23 | /// 24 | #if NET5_0_OR_GREATER 25 | [SupportedOSPlatform("windows")] 26 | #endif 27 | internal class UnsafeMirror : Mirror 28 | { 29 | static void DoRead(SafeFileHandle handle, nint buffer, int bufferLength, out int bytesRead, long offset) 30 | { 31 | handle.SetFilePointer(offset); 32 | handle.ReadFile(buffer, (uint)bufferLength, out bytesRead); 33 | } 34 | 35 | static void DoWrite(SafeFileHandle handle, nint buffer, int bufferLength, out int bytesWritten, long offset) 36 | { 37 | var newpos = Extensions.SetFilePointer(handle, offset); 38 | handle.WriteFile(buffer, (uint)bufferLength, out bytesWritten); 39 | } 40 | 41 | /// 42 | /// Constructs a new unsafe mirror for the specified root path. 43 | /// 44 | /// Root path of mirror. 45 | public UnsafeMirror(ILogger logger, string path) : base(logger, path) { } 46 | 47 | /// 48 | /// Read from file using unmanaged buffers. 49 | /// 50 | public override NtStatus ReadFile(ReadOnlyNativeMemory fileName, NativeMemory buffer, out int bytesRead, long offset, ref DokanFileInfo info) 51 | { 52 | if (info.Context is not FileStream stream) // memory mapped read 53 | { 54 | using (stream = new FileStream(GetPath(fileName), FileMode.Open, System.IO.FileAccess.Read)) 55 | { 56 | DoRead(stream.SafeFileHandle, buffer.Address, buffer.Length, out bytesRead, offset); 57 | } 58 | } 59 | else // normal read 60 | { 61 | #pragma warning disable CA2002 62 | lock (stream) //Protect from overlapped read 63 | #pragma warning restore CA2002 64 | { 65 | DoRead(stream.SafeFileHandle, buffer.Address, buffer.Length, out bytesRead, offset); 66 | } 67 | } 68 | 69 | return Trace($"Unsafe{nameof(ReadFile)}", fileName, info, DokanResult.Success, "out " + bytesRead.ToString(), 70 | offset.ToString(CultureInfo.InvariantCulture)); 71 | } 72 | 73 | /// 74 | /// Write to file using unmanaged buffers. 75 | /// 76 | public override NtStatus WriteFile(ReadOnlyNativeMemory fileName, ReadOnlyNativeMemory buffer, out int bytesWritten, long offset, ref DokanFileInfo info) 77 | { 78 | if (info.Context is not FileStream stream) 79 | { 80 | using (stream = new FileStream(GetPath(fileName), FileMode.Open, FileAccess.Write)) 81 | { 82 | var bytesToCopy = GetNumOfBytesToCopy(buffer.Length, offset, info, stream); 83 | DoWrite(stream.SafeFileHandle, buffer.Address, bytesToCopy, out bytesWritten, offset); 84 | } 85 | } 86 | else 87 | { 88 | #pragma warning disable CA2002 89 | lock (stream) //Protect from overlapped write 90 | #pragma warning restore CA2002 91 | { 92 | var bytesToCopy = GetNumOfBytesToCopy(buffer.Length, offset, info, stream); 93 | DoWrite(stream.SafeFileHandle, buffer.Address, bytesToCopy, out bytesWritten, offset); 94 | } 95 | } 96 | 97 | return Trace($"Unsafe{nameof(WriteFile)}", fileName, info, DokanResult.Success, $"out {bytesWritten}", 98 | offset.ToString(CultureInfo.InvariantCulture)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /sample/DokanNetMirrorLegacy/DokanNetMirrorLegacy.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0;net46;net48 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | MinimumRecommendedRules.ruleset 18 | 19 | false 20 | False 21 | 22 | -------------------------------------------------------------------------------- /sample/DokanNetMirrorLegacy/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Microsoft.Win32.SafeHandles; 4 | 5 | namespace DokanNetMirror 6 | { 7 | public static class Extensions 8 | { 9 | /// returns the new position of the file pointer 10 | /// 11 | /// 12 | public static long SetFilePointer(this SafeFileHandle fileHandle, long offset, System.IO.SeekOrigin seekOrigin = System.IO.SeekOrigin.Begin) 13 | { 14 | if (fileHandle.IsInvalid) 15 | { 16 | throw new InvalidOperationException("can't set pointer on an invalid handle"); 17 | } 18 | 19 | var wasSet = NativeMethods.SetFilePointerEx(fileHandle, offset, out var newposition, seekOrigin); 20 | if (!wasSet) 21 | { 22 | throw new Win32Exception(); 23 | } 24 | 25 | return newposition; 26 | } 27 | 28 | public static void ReadFile(this SafeFileHandle fileHandle, IntPtr buffer, uint bytesToRead, out int bytesRead) 29 | { 30 | if (fileHandle.IsInvalid) 31 | { 32 | throw new InvalidOperationException("can't set pointer on an invalid handle"); 33 | } 34 | 35 | if (!NativeMethods.ReadFile(fileHandle, buffer, bytesToRead, out bytesRead, IntPtr.Zero)) 36 | { 37 | throw new Win32Exception(); 38 | } 39 | } 40 | 41 | public static void WriteFile(this SafeFileHandle fileHandle, IntPtr buffer, uint bytesToWrite, out int bytesWritten) 42 | { 43 | if (fileHandle.IsInvalid) 44 | { 45 | throw new InvalidOperationException("can't set pointer on an invalid handle"); 46 | } 47 | 48 | if (!NativeMethods.WriteFile(fileHandle, buffer, bytesToWrite, out bytesWritten, IntPtr.Zero)) 49 | { 50 | throw new Win32Exception(); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sample/DokanNetMirrorLegacy/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Microsoft.Win32.SafeHandles; 4 | 5 | namespace DokanNetMirror 6 | { 7 | internal static class NativeMethods 8 | { 9 | /// 10 | /// Sets the date and time that the specified file or directory was created, last accessed, or last modified. 11 | /// 12 | /// A to the file or directory. 13 | /// To get the handler, can be used. 14 | /// A Windows File Time that contains the new creation date and time 15 | /// for the file or directory. 16 | /// If the application does not need to change this information, set this parameter to 0. 17 | /// A Windows File Time that contains the new last access date and time 18 | /// for the file or directory. The last access time includes the last time the file or directory 19 | /// was written to, read from, or (in the case of executable files) run. 20 | /// If the application does not need to change this information, set this parameter to 0. 21 | /// A Windows File Time that contains the new last modified date and time 22 | /// for the file or directory. If the application does not need to change this information, 23 | /// set this parameter to 0. 24 | /// If the function succeeds, the return value is true. 25 | /// \see SetFileTime function (MSDN) 26 | [DllImport("kernel32", SetLastError = true)] 27 | public static extern bool SetFileTime(SafeFileHandle hFile, ref long lpCreationTime, ref long lpLastAccessTime, ref long lpLastWriteTime); 28 | 29 | [DllImport("kernel32.dll", SetLastError = true)] 30 | public static extern bool SetFilePointerEx(SafeFileHandle hFile, long liDistanceToMove, out long lpNewFilePointer, [MarshalAs(UnmanagedType.U4)] System.IO.SeekOrigin dwMoveMethod); 31 | 32 | [DllImport("kernel32.dll", SetLastError = true)] 33 | public static extern bool ReadFile(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out int lpNumberOfBytesRead, IntPtr lpOverlapped); 34 | 35 | [DllImport("kernel32.dll", SetLastError = true)] 36 | public static extern bool WriteFile(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToWrite, out int lpNumberOfBytesWritten, IntPtr lpOverlapped); 37 | } 38 | } -------------------------------------------------------------------------------- /sample/DokanNetMirrorLegacy/Notify.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using DokanNet; 4 | 5 | namespace DokanNetMirror 6 | { 7 | internal class Notify : IDisposable 8 | { 9 | private readonly string _sourcePath; 10 | private readonly string _targetPath; 11 | private readonly DokanInstance _dokanInstance; 12 | private readonly FileSystemWatcher _commonFsWatcher; 13 | private readonly FileSystemWatcher _fileFsWatcher; 14 | private readonly FileSystemWatcher _dirFsWatcher; 15 | private bool _disposed; 16 | 17 | public Notify(string mirrorPath, string mountPath, DokanInstance dokanInstance) 18 | { 19 | _sourcePath = mirrorPath; 20 | _targetPath = mountPath; 21 | _dokanInstance = dokanInstance; 22 | 23 | _commonFsWatcher = new FileSystemWatcher(mirrorPath) 24 | { 25 | IncludeSubdirectories = true, 26 | NotifyFilter = NotifyFilters.Attributes | 27 | NotifyFilters.CreationTime | 28 | NotifyFilters.DirectoryName | 29 | NotifyFilters.FileName | 30 | NotifyFilters.LastAccess | 31 | NotifyFilters.LastWrite | 32 | NotifyFilters.Security | 33 | NotifyFilters.Size 34 | }; 35 | 36 | _commonFsWatcher.Changed += OnCommonFileSystemWatcherChanged; 37 | _commonFsWatcher.Created += OnCommonFileSystemWatcherCreated; 38 | _commonFsWatcher.Renamed += OnCommonFileSystemWatcherRenamed; 39 | 40 | _commonFsWatcher.EnableRaisingEvents = true; 41 | 42 | _fileFsWatcher = new FileSystemWatcher(mirrorPath) 43 | { 44 | IncludeSubdirectories = true, 45 | NotifyFilter = NotifyFilters.FileName 46 | }; 47 | 48 | _fileFsWatcher.Deleted += OnCommonFileSystemWatcherFileDeleted; 49 | 50 | _fileFsWatcher.EnableRaisingEvents = true; 51 | 52 | _dirFsWatcher = new FileSystemWatcher(mirrorPath) 53 | { 54 | IncludeSubdirectories = true, 55 | NotifyFilter = NotifyFilters.DirectoryName 56 | }; 57 | 58 | _dirFsWatcher.Deleted += OnCommonFileSystemWatcherDirectoryDeleted; 59 | 60 | _dirFsWatcher.EnableRaisingEvents = true; 61 | } 62 | 63 | private string AlterPathToMountPath(string path) 64 | { 65 | var relativeMirrorPath = path.Substring(_sourcePath.Length).TrimStart('\\'); 66 | 67 | return Path.Combine(_targetPath, relativeMirrorPath); 68 | } 69 | 70 | private void OnCommonFileSystemWatcherFileDeleted(object sender, FileSystemEventArgs e) 71 | { 72 | if (_dokanInstance.IsDisposed) 73 | { 74 | return; 75 | } 76 | 77 | var fullPath = AlterPathToMountPath(e.FullPath); 78 | 79 | Dokan.Notify.Delete(_dokanInstance, fullPath, false); 80 | } 81 | 82 | private void OnCommonFileSystemWatcherDirectoryDeleted(object sender, FileSystemEventArgs e) 83 | { 84 | if (_dokanInstance.IsDisposed) 85 | { 86 | return; 87 | } 88 | 89 | var fullPath = AlterPathToMountPath(e.FullPath); 90 | 91 | Dokan.Notify.Delete(_dokanInstance, fullPath, true); 92 | } 93 | 94 | private void OnCommonFileSystemWatcherChanged(object sender, FileSystemEventArgs e) 95 | { 96 | if (_dokanInstance.IsDisposed) 97 | { 98 | return; 99 | } 100 | 101 | var fullPath = AlterPathToMountPath(e.FullPath); 102 | 103 | Dokan.Notify.Update(_dokanInstance, fullPath); 104 | } 105 | 106 | private void OnCommonFileSystemWatcherCreated(object sender, FileSystemEventArgs e) 107 | { 108 | if (_dokanInstance.IsDisposed) 109 | { 110 | return; 111 | } 112 | 113 | var fullPath = AlterPathToMountPath(e.FullPath); 114 | var isDirectory = Directory.Exists(fullPath); 115 | 116 | Dokan.Notify.Create(_dokanInstance, fullPath, isDirectory); 117 | } 118 | 119 | private void OnCommonFileSystemWatcherRenamed(object sender, RenamedEventArgs e) 120 | { 121 | if (_dokanInstance.IsDisposed) 122 | { 123 | return; 124 | } 125 | 126 | var oldFullPath = AlterPathToMountPath(e.OldFullPath); 127 | var oldDirectoryName = Path.GetDirectoryName(e.OldFullPath); 128 | 129 | var fullPath = AlterPathToMountPath(e.FullPath); 130 | var directoryName = Path.GetDirectoryName(e.FullPath); 131 | 132 | var isDirectory = Directory.Exists(e.FullPath); 133 | var isInSameDirectory = string.Equals(oldDirectoryName, directoryName); 134 | 135 | Dokan.Notify.Rename(_dokanInstance, oldFullPath, fullPath, isDirectory, isInSameDirectory); 136 | } 137 | 138 | protected virtual void Dispose(bool disposing) 139 | { 140 | if (!_disposed) 141 | { 142 | if (disposing) 143 | { 144 | // dispose managed state (managed objects) 145 | _commonFsWatcher.Changed -= OnCommonFileSystemWatcherChanged; 146 | _commonFsWatcher.Created -= OnCommonFileSystemWatcherCreated; 147 | _commonFsWatcher.Renamed -= OnCommonFileSystemWatcherRenamed; 148 | 149 | _commonFsWatcher.Dispose(); 150 | _fileFsWatcher.Dispose(); 151 | _dirFsWatcher.Dispose(); 152 | 153 | } 154 | 155 | // free unmanaged resources (unmanaged objects) and override finalizer 156 | // set large fields to null 157 | _disposed = true; 158 | } 159 | } 160 | 161 | public void Dispose() 162 | { 163 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 164 | Dispose(disposing: true); 165 | GC.SuppressFinalize(this); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /sample/DokanNetMirrorLegacy/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Runtime.Versioning; 4 | using System.Threading.Tasks; 5 | using DokanNet; 6 | using DokanNet.Logging; 7 | 8 | #if NET5_0_OR_GREATER 9 | [assembly: SupportedOSPlatform("windows")] 10 | #endif 11 | 12 | namespace DokanNetMirror 13 | { 14 | internal class Program 15 | { 16 | private const string MirrorKey = "-what"; 17 | private const string MountKey = "-where"; 18 | private const string UseUnsafeKey = "-unsafe"; 19 | 20 | private static async Task Main(string[] args) 21 | { 22 | try 23 | { 24 | var arguments = args 25 | .Select(x => x.Split(['='], 2, StringSplitOptions.RemoveEmptyEntries)) 26 | .ToDictionary(x => x[0], x => x.Length > 1 ? x[1] as object : true, StringComparer.OrdinalIgnoreCase); 27 | 28 | var mirrorPath = arguments.TryGetValue(MirrorKey, out var mirrorPathObj) 29 | ? (string)mirrorPathObj 30 | : @"C:\"; 31 | 32 | var mountPath = arguments.TryGetValue(MountKey, out var mountPathObj) 33 | ? (string)mountPathObj 34 | : @"N:\"; 35 | 36 | var unsafeReadWrite = arguments.ContainsKey(UseUnsafeKey); 37 | 38 | using (var mirrorLogger = new ConsoleLogger("[Mirror] ")) 39 | using (var dokanLogger = new ConsoleLogger("[Dokan] ")) 40 | using (var dokan = new Dokan(dokanLogger)) 41 | { 42 | Console.WriteLine($"Using unsafe methods: {unsafeReadWrite}"); 43 | var mirror = unsafeReadWrite 44 | ? new UnsafeMirror(mirrorLogger, mirrorPath) 45 | : new Mirror(mirrorLogger, mirrorPath); 46 | 47 | var dokanBuilder = new DokanInstanceBuilder(dokan) 48 | .ConfigureLogger(() => dokanLogger) 49 | .ConfigureOptions(options => 50 | { 51 | options.Options = DokanOptions.DebugMode | DokanOptions.EnableNotificationAPI; 52 | options.MountPoint = mountPath; 53 | }); 54 | using var dokanInstance = dokanBuilder.Build(mirror); 55 | using var notify = new Notify(mirrorPath, mountPath, dokanInstance); 56 | Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) => 57 | { 58 | e.Cancel = true; 59 | dokan.RemoveMountPoint(mountPath); 60 | }; 61 | 62 | await dokanInstance.WaitForFileSystemClosedAsync(uint.MaxValue); 63 | } 64 | 65 | Console.WriteLine(@"Success"); 66 | } 67 | catch (DokanException ex) 68 | { 69 | Console.WriteLine(@"Error: " + ex.Message); 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /sample/DokanNetMirrorLegacy/UnsafeMirror.cs: -------------------------------------------------------------------------------- 1 | /* 2 | There is various way we could have implemented this, for example we could have used the Filestream.Seek to move the 3 | filesystem pointer, but for didactic reasons we want to use as much pInvoke as possible. 4 | Also in .NET6 any call through the stream seems to move the pointer back to the position memorized by the stream 5 | https://github.com/dotnet/docs/blob/12473973716efbf21470ecbc96de0bd5e1f65e3b/docs/core/compatibility/core-libraries/6.0/filestream-doesnt-sync-offset-with-os.md 6 | hence we want to avoid touching the filestream once obtained the safefilehandle 7 | */ 8 | 9 | using System; 10 | using System.Globalization; 11 | using System.IO; 12 | using DokanNet; 13 | using DokanNet.Logging; 14 | using Microsoft.Win32.SafeHandles; 15 | 16 | namespace DokanNetMirror 17 | { 18 | /// 19 | /// Implementation of IDokanOperationsUnsafe to demonstrate usage. 20 | /// 21 | internal class UnsafeMirror : Mirror, IDokanOperationsUnsafe 22 | { 23 | static void DoRead(SafeFileHandle handle, IntPtr buffer, uint bufferLength, out int bytesRead, long offset) 24 | { 25 | handle.SetFilePointer(offset); 26 | handle.ReadFile(buffer, bufferLength, out bytesRead); 27 | } 28 | 29 | static void DoWrite(SafeFileHandle handle, IntPtr buffer, uint bufferLength, out int bytesWritten, long offset) 30 | { 31 | _ = Extensions.SetFilePointer(handle, offset); 32 | handle.WriteFile(buffer, bufferLength, out bytesWritten); 33 | } 34 | /// 35 | /// Constructs a new unsafe mirror for the specified root path. 36 | /// 37 | /// Root path of mirror. 38 | public UnsafeMirror(ILogger logger, string path) : base(logger, path) { } 39 | 40 | /// 41 | /// Read from file using unmanaged buffers. 42 | /// 43 | public NtStatus ReadFile(string fileName, IntPtr buffer, uint bufferLength, out int bytesRead, long offset, IDokanFileInfo info) 44 | { 45 | if (info.Context is not FileStream stream) // memory mapped read 46 | { 47 | using (stream = new FileStream(GetPath(fileName), FileMode.Open, System.IO.FileAccess.Read)) 48 | { 49 | DoRead(stream.SafeFileHandle, buffer, bufferLength, out bytesRead, offset); 50 | } 51 | } 52 | else // normal read 53 | { 54 | #pragma warning disable CA2002 55 | lock (stream) //Protect from overlapped read 56 | #pragma warning restore CA2002 57 | { 58 | DoRead(stream.SafeFileHandle, buffer, bufferLength, out bytesRead, offset); 59 | } 60 | } 61 | 62 | return Trace($"Unsafe{nameof(ReadFile)}", fileName, info, DokanResult.Success, "out " + bytesRead.ToString(), 63 | offset.ToString(CultureInfo.InvariantCulture)); 64 | } 65 | 66 | /// 67 | /// Write to file using unmanaged buffers. 68 | /// 69 | public NtStatus WriteFile(string fileName, IntPtr buffer, uint bufferLength, out int bytesWritten, long offset, IDokanFileInfo info) 70 | { 71 | if (info.Context is not FileStream stream) 72 | { 73 | using (stream = new FileStream(GetPath(fileName), FileMode.Open, System.IO.FileAccess.Write)) 74 | { 75 | var bytesToCopy = (uint)GetNumOfBytesToCopy((int)bufferLength, offset, info, stream); 76 | DoWrite(stream.SafeFileHandle, buffer, bytesToCopy, out bytesWritten, offset); 77 | } 78 | } 79 | else 80 | { 81 | #pragma warning disable CA2002 82 | lock (stream) //Protect from overlapped write 83 | #pragma warning restore CA2002 84 | { 85 | var bytesToCopy = (uint)GetNumOfBytesToCopy((int)bufferLength, offset, info, stream); 86 | DoWrite(stream.SafeFileHandle, buffer, bytesToCopy, out bytesWritten, offset); 87 | } 88 | } 89 | 90 | return Trace($"Unsafe{nameof(WriteFile)}", fileName, info, DokanResult.Success, "out " + bytesWritten.ToString(), 91 | offset.ToString(CultureInfo.InvariantCulture)); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /sample/RegistryFS/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Versioning; 3 | using DokanNet; 4 | using DokanNet.Logging; 5 | 6 | #if NET5_0_OR_GREATER 7 | [assembly: SupportedOSPlatform("windows")] 8 | #endif 9 | 10 | namespace RegistryFS 11 | { 12 | internal class Program 13 | { 14 | private static void Main() 15 | { 16 | try 17 | { 18 | using var mre = new System.Threading.ManualResetEvent(false); 19 | using var dokanLogger = new ConsoleLogger("[Dokan] "); 20 | using var dokan = new Dokan(dokanLogger); 21 | Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) => 22 | { 23 | e.Cancel = true; 24 | mre.Set(); 25 | }; 26 | 27 | var rfs = new RFS(); 28 | var dokanBuilder = new DokanInstanceBuilder(dokan) 29 | .ConfigureOptions(options => 30 | { 31 | options.Options = DokanOptions.DebugMode | DokanOptions.StderrOutput; 32 | options.MountPoint = "r:\\"; 33 | }); 34 | using (var dokanInstance = dokanBuilder.Build(rfs)) 35 | { 36 | mre.WaitOne(); 37 | } 38 | 39 | Console.WriteLine(@"Success"); 40 | } 41 | catch (DokanException ex) 42 | { 43 | Console.WriteLine(@"Error: " + ex.Message); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sample/RegistryFS/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "RegistryFS (net4.6)": { 4 | "commandName": "Project" 5 | }, 6 | "RegistryFS (net4.0)": { 7 | "commandName": "Executable", 8 | "executablePath": "$(ProjectDir)\\bin\\$(Configuration)\\net4.0\\win\\RegistryFS.exe" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /sample/RegistryFS/RegistryFS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0;net46;net48 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MinimumRecommendedRules.ruleset 14 | 15 | false 16 | 17 | --------------------------------------------------------------------------------