├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── fsmemfs.sln ├── fsmemfs.tests ├── App.config ├── AssemblyInfo.fs ├── FileTreeTests.fs ├── fsmemfs.tests.fsproj └── packages.config └── fsmemfs ├── AssemblyInfo.fs ├── FileSystemTypes.fs ├── Logging.fs ├── MemoryFileSystem.fs ├── MemoryFileSystemService.fs ├── Program.fs ├── fsmemfs.fsproj └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | !wtrace/binaries/**/* 162 | 163 | # Microsoft Azure Build Output 164 | csx/ 165 | *.build.csdef 166 | 167 | # Microsoft Azure Emulator 168 | ecf/ 169 | rcf/ 170 | 171 | # Microsoft Azure ApplicationInsights config file 172 | ApplicationInsights.config 173 | 174 | # Windows Store app package directory 175 | AppPackages/ 176 | BundleArtifacts/ 177 | 178 | # Visual Studio cache files 179 | # files ending in .cache can be ignored 180 | *.[Cc]ache 181 | # but keep track of directories ending in .cache 182 | !*.[Cc]ache/ 183 | 184 | # Others 185 | ClientBin/ 186 | [Ss]tyle[Cc]op.* 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # RIA/Silverlight projects 197 | Generated_Code/ 198 | 199 | # Backup & report files from converting an old project file 200 | # to a newer Visual Studio version. Backup files are not needed, 201 | # because we have git ;-) 202 | _UpgradeReport_Files/ 203 | Backup*/ 204 | UpgradeLog*.XML 205 | UpgradeLog*.htm 206 | 207 | # SQL Server files 208 | *.mdf 209 | *.ldf 210 | 211 | # Business Intelligence projects 212 | *.rdl.data 213 | *.bim.layout 214 | *.bim_*.settings 215 | 216 | # Microsoft Fakes 217 | FakesAssemblies/ 218 | 219 | # GhostDoc plugin setting file 220 | *.GhostDoc.xml 221 | 222 | # Node.js Tools for Visual Studio 223 | .ntvs_analysis.dat 224 | 225 | # Visual Studio 6 build log 226 | *.plg 227 | 228 | # Visual Studio 6 workspace options file 229 | *.opt 230 | 231 | # Visual Studio LightSwitch build output 232 | **/*.HTMLClient/GeneratedArtifacts 233 | **/*.DesktopClient/GeneratedArtifacts 234 | **/*.DesktopClient/ModelManifest.xml 235 | **/*.Server/GeneratedArtifacts 236 | **/*.Server/ModelManifest.xml 237 | _Pvt_Extensions 238 | 239 | # LightSwitch generated files 240 | GeneratedArtifacts/ 241 | ModelManifest.xml 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | 246 | # FAKE - F# Make 247 | .fake/ 248 | 249 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2019 Sebastian Solnica 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # fsmemfs 3 | 4 | Fsmemfs is a simple memory file system based on [WinFsp](https://github.com/billziss-gh/winfsp) (WinFsp - Windows File System Proxy, Copyright (C) Bill Zissimopoulos). I wrote it to test various options of the WinFsp and also to learn F#. It works but it's not fully tested so please be warned. 5 | 6 | The available options are: 7 | 8 | ``` 9 | Usage: fsmemfs OPTIONS 10 | 11 | Options: 12 | -m VALUE [required] mount point, could be a drive (G:) or a folder (C:\memfs) 13 | -v [optional] enable verbose log 14 | -l VALUE [optional] a path to a file where fsmemfs should write logs 15 | -D VALUE [optional] a path to a file where WinFsp should write debug logs 16 | -F VALUE [optional] a filesystem name, if not set it will be 'fsmemfs' 17 | ``` 18 | 19 | To create a new G: volume, simply run: `fsmemfs -m G:`. 20 | 21 | Additionally, to see some logs from the kernel driver (-D) and user-mode driver (-l), run: `fsmemfs -v -l d:\temp\fsmemfs.log -D d:\temp\fsmemfs-driver.log -m G:`. 22 | -------------------------------------------------------------------------------- /fsmemfs.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "fsmemfs", "fsmemfs\fsmemfs.fsproj", "{E532858D-56AD-42C3-A9A4-1C2B1D5D1AFD}" 4 | EndProject 5 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "fsmemfs.tests", "fsmemfs.tests\fsmemfs.tests.fsproj", "{FC3EF100-4CCA-4DD8-8E79-23C0F2ED551A}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {E532858D-56AD-42C3-A9A4-1C2B1D5D1AFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {E532858D-56AD-42C3-A9A4-1C2B1D5D1AFD}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {E532858D-56AD-42C3-A9A4-1C2B1D5D1AFD}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {E532858D-56AD-42C3-A9A4-1C2B1D5D1AFD}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {FC3EF100-4CCA-4DD8-8E79-23C0F2ED551A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {FC3EF100-4CCA-4DD8-8E79-23C0F2ED551A}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {FC3EF100-4CCA-4DD8-8E79-23C0F2ED551A}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {FC3EF100-4CCA-4DD8-8E79-23C0F2ED551A}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /fsmemfs.tests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /fsmemfs.tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LowLevelDesign.FsMemFs 2 | 3 | open System.Reflection 4 | open System.Runtime.InteropServices 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [] 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [] 36 | [] 37 | [] 38 | 39 | do 40 | () -------------------------------------------------------------------------------- /fsmemfs.tests/FileTreeTests.fs: -------------------------------------------------------------------------------- 1 | module LowLevelDesign.FsMemFs.Tests.FileTreeTests 2 | 3 | open FsUnit 4 | open LowLevelDesign.FsMemFs 5 | open NUnit.Framework 6 | open System 7 | open System.Collections.Generic 8 | open System.IO 9 | 10 | let fileProps = { FileSize = 0UL; AllocationSize = 0UL; FileData = [||]; FileIndex = 0UL } 11 | 12 | let createSampleTree() = 13 | let aNodes = [ 14 | "aa", FileNode ({ Name = "aa" 15 | CreatedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 16 | LastModifiedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 17 | FileAttributes = uint32 FileAttributes.Archive 18 | }, fileProps) 19 | "ab", FileNode ({ Name = "ab" 20 | CreatedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 21 | LastModifiedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 22 | FileAttributes = uint32 FileAttributes.Archive 23 | }, fileProps) 24 | ] |> dict |> Dictionary 25 | 26 | let nodes = [ 27 | "a", FolderNode ({ Name = "a" 28 | CreatedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 29 | LastModifiedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 30 | FileAttributes = uint32 FileAttributes.Directory }, aNodes) 31 | "b", FolderNode ({ Name = "b" 32 | CreatedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 33 | LastModifiedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 34 | FileAttributes = uint32 FileAttributes.Directory }, aNodes) 35 | ] |> dict |> Dictionary 36 | 37 | FileTree (FolderNode ({ Name = "root" 38 | CreatedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 39 | LastModifiedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 40 | FileAttributes = uint32 FileAttributes.Directory }, nodes)) 41 | 42 | [] 43 | let ``Find node in a tree``() = 44 | let tree = createSampleTree() 45 | 46 | let root = tree.FindNode("\\") 47 | match root with 48 | | Some(FolderNode({ Name = name }, _)) -> name |> should equal "root" 49 | | _ -> false |> should be True 50 | 51 | let root = tree.FindNode("") 52 | match root with 53 | | Some(FolderNode({ Name = name }, _)) -> name |> should equal "root" 54 | | _ -> false |> should be True 55 | 56 | tree.FindNode("b/c") |> should equal None 57 | 58 | match (tree.FindNode("b")) with 59 | | Some(FolderNode({ Name = name }, _)) -> name |> should equal "b" 60 | | _ -> false |> should be True 61 | 62 | match (tree.FindNode("/b")) with 63 | | Some(FolderNode({ Name = name }, _)) -> name |> should equal "b" 64 | | _ -> false |> should be True 65 | 66 | match (tree.FindNode("a\\aa")) with 67 | | Some(FileNode({ Name = name }, _)) -> name |> should equal "aa" 68 | | _ -> false |> should be True 69 | 70 | match (tree.FindNode("a\\///aa")) with 71 | | Some(FileNode({ Name = name }, _)) -> name |> should equal "aa" 72 | | _ -> false |> should be True 73 | 74 | tree.FindNode("a\\cc") |> should equal None 75 | 76 | tree.FindNode("c") |> should equal None 77 | 78 | [] 79 | let ``Find nearest node in a tree``() = 80 | let tree = createSampleTree() 81 | 82 | match tree.FindParentNode("") with 83 | | Some(FolderNode({ Name = name }, _)) -> name |> should equal "root" 84 | | _ -> false |> should be True 85 | 86 | match tree.FindParentNode("b/c") with 87 | | Some(FolderNode({ Name = name }, _)) -> 88 | name |> should equal "b" 89 | | _ -> false |> should be True 90 | 91 | match (tree.FindParentNode("\\\\a\\aa")) with 92 | | Some(FolderNode({ Name = name }, _)) -> 93 | name |> should equal "a" 94 | | _ -> false |> should be True 95 | 96 | match (tree.FindParentNode("\\a\\desktop")) with 97 | | Some(FolderNode({ Name = name }, _)) -> 98 | name |> should equal "a" 99 | | _ -> false |> should be True 100 | 101 | [] 102 | let ``Add and update a node in a tree``() = 103 | let tree = createSampleTree() 104 | 105 | let newFile = FileNode ({ Name = "baaa" 106 | CreatedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 107 | LastModifiedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 108 | FileAttributes = uint32 FileAttributes.Archive 109 | }, fileProps) 110 | tree.AddOrUpdateNode "b/ba/baa/baaa" newFile 111 | tree.FindNode "b/ba/baa/baaa" |> should equal (Some newFile) 112 | 113 | let newFile = FileNode ({ Name = "baaa" 114 | CreatedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 115 | LastModifiedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 116 | FileAttributes = uint32 FileAttributes.Archive 117 | }, fileProps) 118 | tree.AddOrUpdateNode "b/ba/baa/baaa" newFile 119 | tree.FindNode "b/ba/baa/baaa" |> should equal (Some newFile) 120 | 121 | [] 122 | let ``Remove a node from a tree``() = 123 | let tree = createSampleTree() 124 | 125 | let newFile = FileNode ({ Name = "baaa" 126 | CreatedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 127 | LastModifiedFileTimeUtc = uint64 (DateTime.Now.ToFileTimeUtc()) 128 | FileAttributes = uint32 FileAttributes.Archive 129 | }, fileProps) 130 | tree.AddOrUpdateNode "b/ba/baa/baaa" newFile 131 | tree.FindNode "b/ba/baa/baaa" |> should equal (Some newFile) 132 | tree.RemoveNode "b/ba/baa" |> should be True 133 | tree.FindNode "b/ba/baa/baaa" |> should equal (None) 134 | -------------------------------------------------------------------------------- /fsmemfs.tests/fsmemfs.tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {FC3EF100-4CCA-4DD8-8E79-23C0F2ED551A} 10 | Library 11 | LowLevelDesign.FsMemFs.Tests 12 | fsmemfs.tests 13 | v4.6.2 14 | true 15 | 16 | 17 | true 18 | portable 19 | false 20 | false 21 | bin\$(Configuration)\ 22 | DEBUG;TRACE 23 | 3 24 | --warnon:1182 25 | 26 | 27 | pdbonly 28 | true 29 | true 30 | bin\$(Configuration)\ 31 | TRACE 32 | 3 33 | --warnon:1182 34 | 35 | 36 | 37 | ..\packages\FSharp.Core.4.6.2\lib\net45\FSharp.Core.dll 38 | True 39 | 40 | 41 | ..\packages\FsUnit.3.4.0\lib\net46\FsUnit.NUnit.dll 42 | True 43 | 44 | 45 | 46 | ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll 47 | True 48 | 49 | 50 | 51 | 52 | 53 | ..\..\..\..\Program Files (x86)\WinFsp\bin\winfsp-msil.dll 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | {e532858d-56ad-42c3-a9a4-1c2b1d5d1afd} 64 | fsmemfs 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /fsmemfs.tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /fsmemfs/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LowLevelDesign.FsMemFs 2 | 3 | open System.Reflection 4 | open System.Runtime.InteropServices 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [] 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [] 36 | [] 37 | [] 38 | 39 | do 40 | () -------------------------------------------------------------------------------- /fsmemfs/FileSystemTypes.fs: -------------------------------------------------------------------------------- 1 | namespace LowLevelDesign.FsMemFs 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.IO 6 | open System.Text.RegularExpressions 7 | 8 | type NodeProperties = { 9 | Name : string 10 | CreatedFileTimeUtc : uint64 11 | LastModifiedFileTimeUtc : uint64 12 | FileAttributes : uint32 13 | } 14 | 15 | type FileNodeProperties = { 16 | FileSize : uint64 17 | AllocationSize : uint64 18 | FileData : byte array 19 | FileIndex : uint64 20 | } 21 | 22 | [] 23 | type TreeNode = 24 | | FileNode of NodeProperties * FileNodeProperties 25 | | FolderNode of NodeProperties * Dictionary 26 | 27 | 28 | exception FolderDoesNotExist of string 29 | 30 | type FileTree(root : TreeNode) = 31 | 32 | let splitPath (path : string) = path.Split([| '/'; '\\' |], StringSplitOptions.RemoveEmptyEntries) 33 | 34 | let rec findNode node index (splits : string array) = 35 | match node with 36 | | FileNode _ -> Some node 37 | | FolderNode _ when index = splits.Length -> Some node 38 | | FolderNode(_, children) -> 39 | let p = splits.[index] 40 | match children.TryGetValue(p) with 41 | | (true, child) -> findNode child (index + 1) splits 42 | | (false, _) -> None 43 | 44 | member this.AddOrUpdateNode (path : string) node = 45 | let rec findOrCreateFolderNode node index (splits : string array) = 46 | match node with 47 | | FileNode _ -> raise (FolderDoesNotExist(sprintf "should be folder but is file: '%s'" path)) 48 | | FolderNode _ when index = splits.Length -> node 49 | | FolderNode(_, children) -> 50 | let p = splits.[index] 51 | match children.TryGetValue(p) with 52 | | (true, child) -> findOrCreateFolderNode child (index + 1) splits 53 | | (false, _) -> 54 | let child = FolderNode ({ Name = p 55 | CreatedFileTimeUtc = uint64 (DateTime.UtcNow.ToFileTimeUtc()) 56 | LastModifiedFileTimeUtc = uint64 (DateTime.UtcNow.ToFileTimeUtc()) 57 | FileAttributes = uint32 FileAttributes.Directory 58 | }, Dictionary(StringComparer.OrdinalIgnoreCase)) 59 | children.[p] <- child 60 | findOrCreateFolderNode child (index + 1) splits 61 | 62 | let splits = splitPath path 63 | assert (splits.Length <> 0) 64 | let parent = if splits.Length = 1 then root 65 | else findOrCreateFolderNode root 0 splits.[0..(splits.Length - 2)] 66 | match parent with 67 | | FolderNode(_, children) -> 68 | match node with 69 | | FileNode({ Name = name }, _) 70 | | FolderNode({ Name = name }, _) -> children.[name] <- node 71 | | _ -> assert false; invalidOp "should never happen" 72 | 73 | member this.FindNode(path : string) = 74 | findNode root 0 (splitPath path) 75 | 76 | member this.FindParentNode(path : string) = 77 | let splits = splitPath path 78 | if splits.Length <= 1 then 79 | Some root // root is the parent of root 80 | else 81 | findNode root 0 splits.[0..(splits.Length - 2)] 82 | 83 | member this.RemoveNode(path : string) = 84 | let endIndex = path.LastIndexOfAny([| '/'; '\\' |]) 85 | let name = if endIndex = -1 then path else path.Substring(endIndex + 1) 86 | let parentPath = if endIndex = -1 then "" else path.Substring(0, endIndex) 87 | let parent = this.FindNode parentPath 88 | match parent with 89 | | Some(FolderNode(_, children)) -> children.Remove(name) |> ignore 90 | | _ -> raise (FolderDoesNotExist parentPath) 91 | 92 | 93 | type DirectorySearchContext(parent : TreeNode, node : TreeNode, pattern : string, marker : string) = 94 | 95 | let pattern = if pattern = null then "*" else pattern 96 | let marker = if marker = null then "" else marker 97 | 98 | let mutable index = 0 99 | 100 | let entries = 101 | match node with 102 | | FileNode _ -> assert false; [||] 103 | | FolderNode(_, children) -> 104 | // TODO implement simple string matching instead of using regexes 105 | let regexMatch s = 106 | let regex = "^" + Regex.Escape(pattern).Replace("~", "\\~").Replace(">", ".").Replace("<", ".*") + "$" 107 | Regex.IsMatch(s, regex, RegexOptions.IgnoreCase) 108 | 109 | let i = if pattern = "*" || pattern = "." || pattern = "?" then 110 | [| (".", node); ("..", parent) |] 111 | else [||] 112 | 113 | let items = children.Values 114 | |> Seq.choose (fun v -> 115 | match v with 116 | | FileNode(props, _) 117 | | FolderNode(props, _) -> 118 | if pattern = "*" || regexMatch props.Name then 119 | Some(props.Name, v) 120 | else None) 121 | |> Seq.toArray |> Array.append i 122 | 123 | if marker <> "" then 124 | index <- (items |> Array.findIndex (fun (name, _) -> name = marker)) + 1 125 | 126 | items 127 | 128 | member this.Entries = entries 129 | 130 | member this.Index 131 | with get () = index 132 | and set (value) = index <- value 133 | 134 | -------------------------------------------------------------------------------- /fsmemfs/Logging.fs: -------------------------------------------------------------------------------- 1 | module LowLevelDesign.FsMemFs.Logging 2 | 3 | open System.Diagnostics 4 | 5 | let Logger = TraceSource("applogger", SourceLevels.Verbose) 6 | 7 | Logger.Listeners.Remove("default") 8 | Logger.Listeners.Add(new ConsoleTraceListener()) |> ignore 9 | 10 | let private buildMessage source action message = 11 | sprintf "%s [%s] %s" source action message 12 | 13 | let private logEvent (evt : TraceEventType) source action message = 14 | let log = buildMessage source action message 15 | Logger.TraceEvent(evt, 0, log) 16 | 17 | let logVerbose = logEvent TraceEventType.Verbose 18 | 19 | let logInfo = logEvent TraceEventType.Information 20 | 21 | let logWarning = logEvent TraceEventType.Warning 22 | 23 | let logError = logEvent TraceEventType.Error 24 | 25 | let logCritical = logEvent TraceEventType.Critical 26 | 27 | -------------------------------------------------------------------------------- /fsmemfs/MemoryFileSystem.fs: -------------------------------------------------------------------------------- 1 | namespace LowLevelDesign.FsMemFs 2 | 3 | open Fsp 4 | open System 5 | open System.Collections.Generic 6 | open System.IO 7 | open System.Runtime.InteropServices 8 | 9 | type FspFileInfo = Fsp.Interop.FileInfo 10 | 11 | type MemoryFileSystem(fileSystemName : string) = 12 | inherit FileSystemBase() 13 | 14 | let className = typedefof.Name 15 | 16 | let MEMFS_SECTOR_SIZE = 512us; 17 | let MEMFS_SECTORS_PER_ALLOCATION_UNIT = 1us; 18 | 19 | let maxFileNodes = 1024UL; 20 | let maxFileSize = 16 * 1024 * 1024; 21 | 22 | let rootNode = (FolderNode ({ Name = "root" 23 | CreatedFileTimeUtc = uint64 (DateTime.UtcNow.ToFileTimeUtc()) 24 | LastModifiedFileTimeUtc = uint64 (DateTime.UtcNow.ToFileTimeUtc()) 25 | FileAttributes = uint32 FileAttributes.Directory }, 26 | Dictionary(StringComparer.OrdinalIgnoreCase))) 27 | let fileTree = FileTree(rootNode) 28 | 29 | let mutable globalFileIndex = 1UL 30 | 31 | (* ********************************************* * 32 | * HELPERS * 33 | * ********************************************* *) 34 | 35 | let getFileInfo node = 36 | match node with 37 | | FileNode(props, fileProps) -> 38 | FspFileInfo ( 39 | FileAttributes = props.FileAttributes, 40 | FileSize = fileProps.FileSize, 41 | AllocationSize = fileProps.AllocationSize, 42 | CreationTime = props.CreatedFileTimeUtc, 43 | LastAccessTime = props.LastModifiedFileTimeUtc, 44 | LastWriteTime = props.LastModifiedFileTimeUtc, 45 | ChangeTime = props.LastModifiedFileTimeUtc, 46 | ReparseTag = 0u, 47 | IndexNumber = fileProps.FileIndex, 48 | HardLinks = 0u 49 | ) 50 | | FolderNode(props, _) -> 51 | FspFileInfo ( 52 | FileAttributes = uint32 props.FileAttributes, 53 | FileSize = 0UL, 54 | AllocationSize = 0UL, 55 | CreationTime = props.CreatedFileTimeUtc, 56 | LastAccessTime = props.LastModifiedFileTimeUtc, 57 | LastWriteTime = props.LastModifiedFileTimeUtc, 58 | ChangeTime = props.LastModifiedFileTimeUtc, 59 | ReparseTag = 0u, 60 | IndexNumber = 0UL, 61 | HardLinks = 0u 62 | ) 63 | 64 | let setFileSize (node, newSize : uint64, setAllocationSize : bool) = 65 | let changeAllocationSize (props, fileProps) newSize = 66 | if newSize <= uint64 maxFileSize then 67 | try 68 | let data = Array.zeroCreate (int32 newSize) 69 | let len = int32 (min newSize fileProps.AllocationSize) 70 | Array.Copy(fileProps.FileData, data, len) 71 | let node = FileNode (props, { 72 | FileData = data 73 | FileSize = uint64 len 74 | AllocationSize = newSize 75 | FileIndex = fileProps.FileIndex 76 | }) 77 | Ok node 78 | with 79 | | :? OutOfMemoryException -> 80 | Error FileSystemBase.STATUS_INSUFFICIENT_RESOURCES 81 | else 82 | Error FileSystemBase.STATUS_DISK_FULL 83 | 84 | let changeFileSize (props, fileProps) newSize = 85 | let fileSize = fileProps.FileSize 86 | if newSize > fileSize then 87 | Array.Clear(fileProps.FileData, int32 fileSize, int32 (newSize - fileSize)) 88 | let node = FileNode(props, { fileProps with FileSize = newSize }) 89 | Ok node 90 | 91 | match node with 92 | | FileNode(props, fileProps) as node -> 93 | match fileProps with 94 | | { AllocationSize = allocationSize } when setAllocationSize && allocationSize <> newSize -> 95 | changeAllocationSize (props, fileProps) newSize 96 | 97 | | { FileSize = fileSize; AllocationSize = allocationSize } when not setAllocationSize && fileSize <> newSize -> 98 | if allocationSize < newSize then 99 | let allocationUnit = uint64 (MEMFS_SECTOR_SIZE * MEMFS_SECTORS_PER_ALLOCATION_UNIT) 100 | let allocationSize = (newSize + allocationUnit - 1UL) / allocationUnit * allocationUnit 101 | match changeAllocationSize (props, fileProps) allocationSize with 102 | | Ok(FileNode(props, fileProps)) -> 103 | changeFileSize (props, fileProps) newSize 104 | | Ok(FolderNode _) -> 105 | assert false; Error FileSystemBase.STATUS_INVALID_DEVICE_REQUEST 106 | | err -> err 107 | else 108 | changeFileSize (props, fileProps) newSize 109 | 110 | | _ -> 111 | Ok node 112 | | _ -> 113 | Error FileSystemBase.STATUS_OBJECT_PATH_NOT_FOUND 114 | 115 | (* ********************************************* * 116 | * CREATE / OPEN * 117 | * ********************************************* *) 118 | 119 | override this.Open(path, _, _, _, fileDesc, fileInfo, _) = 120 | Logging.logVerbose className "Open" path 121 | 122 | fileDesc <- path 123 | 124 | match fileTree.FindNode path with 125 | | Some node -> 126 | fileInfo <- getFileInfo node 127 | FileSystemBase.STATUS_SUCCESS 128 | | None -> 129 | FileSystemBase.STATUS_NOT_FOUND 130 | 131 | override this.Create(path, createOptions, _, fileAttributes, _, allocationSize, _, fileDesc, fileInfo, _) = 132 | Logging.logVerbose className "Create" path 133 | 134 | fileDesc <- path 135 | 136 | let isFolder = createOptions &&& FileSystemBase.FILE_DIRECTORY_FILE <> 0u 137 | let props = { 138 | Name = Path.GetFileName(path.TrimEnd('/', '\\')) 139 | CreatedFileTimeUtc = uint64 (DateTime.UtcNow.ToFileTimeUtc()) 140 | LastModifiedFileTimeUtc = uint64 (DateTime.UtcNow.ToFileTimeUtc()) 141 | FileAttributes = if isFolder then fileAttributes else fileAttributes ||| uint32 FileAttributes.Archive 142 | } 143 | if isFolder then 144 | let node = FolderNode ({ props with FileAttributes = props.FileAttributes }, 145 | Dictionary(StringComparer.OrdinalIgnoreCase)) 146 | 147 | fileTree.AddOrUpdateNode path node 148 | fileInfo <- getFileInfo node 149 | FileSystemBase.STATUS_SUCCESS 150 | else 151 | let node = FileNode (props, { 152 | FileSize = 0UL 153 | AllocationSize = 0UL 154 | FileData = [||] 155 | FileIndex = globalFileIndex 156 | }) 157 | match setFileSize (node, allocationSize, true) with 158 | | Ok node -> 159 | fileTree.AddOrUpdateNode path node 160 | fileInfo <- getFileInfo node 161 | globalFileIndex <- globalFileIndex + 1UL 162 | FileSystemBase.STATUS_SUCCESS 163 | | Error err -> err 164 | 165 | (* ********************************************* * 166 | * READS * 167 | * ********************************************* *) 168 | 169 | override this.Read(_, fileDesc, buffer, offset, length, bytesTransferred) = 170 | let path : string = downcast fileDesc 171 | Logging.logVerbose className "Read" path 172 | 173 | match fileTree.FindNode path with 174 | | None | Some(FolderNode _) -> assert false; FileSystemBase.STATUS_INVALID_DEVICE_REQUEST 175 | | Some(FileNode(_, fileProps)) -> 176 | if offset >= fileProps.FileSize then 177 | bytesTransferred <- 0u 178 | FileSystemBase.STATUS_END_OF_FILE 179 | else 180 | let endOffset : uint64 = min fileProps.FileSize (offset + uint64 length) 181 | let bytesRead = int32 (endOffset - offset) 182 | Marshal.Copy(fileProps.FileData, int offset, buffer, bytesRead) 183 | bytesTransferred <- uint32 bytesRead 184 | FileSystemBase.STATUS_SUCCESS 185 | 186 | override this.ReadDirectoryEntry(_, fileDesc, pattern, marker, context, fileName, fileInfo) = 187 | let path : string = downcast fileDesc 188 | Logging.logVerbose className "ReadDirectoryEntry" path 189 | 190 | match fileTree.FindNode path with 191 | | None | Some(FileNode _) -> false 192 | | Some(FolderNode _ as node) -> 193 | if context = null then 194 | let parent = fileTree.FindParentNode path 195 | assert parent.IsSome 196 | context <- DirectorySearchContext(parent.Value, node, pattern, marker) 197 | let searchContext : DirectorySearchContext = downcast context 198 | 199 | let index = searchContext.Index 200 | if searchContext.Entries.Length > index then 201 | searchContext.Index <- index + 1 202 | let name, node = searchContext.Entries.[index] 203 | fileName <- name 204 | fileInfo <- getFileInfo node 205 | true 206 | else 207 | false 208 | 209 | override this.GetFileInfo(_, fileDesc, fileInfo) = 210 | let path : string = downcast fileDesc 211 | Logging.logVerbose className "GetFileInfo" path 212 | 213 | match fileTree.FindNode path with 214 | | Some node -> 215 | fileInfo <- getFileInfo node 216 | FileSystemBase.STATUS_SUCCESS 217 | | None -> 218 | FileSystemBase.STATUS_NOT_FOUND 219 | 220 | (* ********************************************* * 221 | * WRITES * 222 | * ********************************************* *) 223 | 224 | override this.Write(_, fileDesc, buffer, offset, 225 | length, writeToEndOfFile, constrainedIo, 226 | bytesTransferred, fileInfo) = 227 | let path : string = downcast fileDesc 228 | Logging.logVerbose className "Write" path 229 | 230 | match fileTree.FindNode path with 231 | | Some(FileNode(_, fileProps)) when constrainedIo && offset >= fileProps.FileSize -> 232 | FileSystemBase.STATUS_SUCCESS 233 | | Some(FileNode(_, fileProps) as node) -> 234 | let offset = if not constrainedIo && writeToEndOfFile then fileProps.FileSize else offset 235 | let endOffset = if constrainedIo then min fileProps.FileSize (offset + uint64 length) 236 | else offset + uint64 length 237 | if endOffset > fileProps.FileSize then 238 | match setFileSize (node, endOffset, false) with 239 | | Ok(FileNode(_, fileProps) as node) -> 240 | fileTree.AddOrUpdateNode path node 241 | 242 | let len = int32 (endOffset - offset) 243 | Marshal.Copy(buffer, fileProps.FileData, int32 offset, len) 244 | bytesTransferred <- uint32 len 245 | 246 | fileInfo <- getFileInfo node 247 | FileSystemBase.STATUS_SUCCESS 248 | | Ok(FolderNode _) -> assert false; FileSystemBase.STATUS_INVALID_DEVICE_REQUEST 249 | | Error err -> err 250 | else 251 | let len = int32 (endOffset - offset) 252 | Marshal.Copy(buffer, fileProps.FileData, int32 offset, len) 253 | bytesTransferred <- uint32 len 254 | 255 | fileInfo <- getFileInfo node 256 | FileSystemBase.STATUS_SUCCESS 257 | | _ -> 258 | assert false 259 | FileSystemBase.STATUS_INVALID_DEVICE_REQUEST 260 | 261 | override this.Overwrite(_, fileDesc, fileAttributes, 262 | replaceFileAttributes, allocationSize, fileInfo) = 263 | let path : string = downcast fileDesc 264 | Logging.logVerbose className "Overwrite" path 265 | 266 | let node = fileTree.FindNode path 267 | assert node.IsSome 268 | match setFileSize (node.Value, allocationSize, true) with 269 | | Ok(FileNode(props, fileProps)) -> 270 | let fileAttributes = if replaceFileAttributes then 271 | fileAttributes ||| uint32 FileAttributes.Archive 272 | else 273 | props.FileAttributes ||| fileAttributes ||| uint32 FileAttributes.Archive 274 | 275 | let node = FileNode ({ Name = props.Name 276 | CreatedFileTimeUtc = uint64 (DateTime.UtcNow.ToFileTimeUtc()) 277 | LastModifiedFileTimeUtc = uint64 (DateTime.UtcNow.ToFileTimeUtc()) 278 | FileAttributes = fileAttributes }, { fileProps with FileSize = 0UL }) 279 | 280 | fileTree.AddOrUpdateNode path node 281 | fileInfo <- getFileInfo node 282 | FileSystemBase.STATUS_SUCCESS 283 | | Ok(FolderNode _) -> assert false; FileSystemBase.STATUS_INVALID_DEVICE_REQUEST 284 | | Error err -> err 285 | 286 | override this.SetFileSize(_, fileDesc, newSize, setAllocationSize, fileInfo) = 287 | let path : string = downcast fileDesc 288 | Logging.logVerbose className "SetFileSize" path 289 | 290 | let node = fileTree.FindNode path 291 | assert node.IsSome 292 | match setFileSize (node.Value, newSize, setAllocationSize) with 293 | | Ok node -> 294 | fileTree.AddOrUpdateNode path node 295 | fileInfo <- getFileInfo node 296 | FileSystemBase.STATUS_SUCCESS 297 | | Error status -> status 298 | 299 | override this.Rename(_, fileDesc, fileName, newFileName, replaceIfExists) = 300 | let path : string = downcast fileDesc 301 | Logging.logVerbose className "Rename" (sprintf "'%s' -> '%s'" path newFileName) 302 | 303 | assert (String.Equals(path, fileName, StringComparison.OrdinalIgnoreCase)) 304 | 305 | match fileTree.FindNode path with 306 | | Some currentNode -> 307 | match fileTree.FindNode newFileName with 308 | | Some _ when not replaceIfExists -> 309 | FileSystemBase.STATUS_OBJECT_NAME_COLLISION 310 | | Some(FolderNode _) -> 311 | FileSystemBase.STATUS_ACCESS_DENIED 312 | | newNode -> 313 | if newNode.IsSome then 314 | fileTree.RemoveNode newFileName 315 | 316 | fileTree.RemoveNode path 317 | 318 | let name = Path.GetFileName(newFileName.TrimEnd([| '/'; '\\' |])) 319 | let newNode = match currentNode with 320 | | FileNode(props, fileProps) -> FileNode({ props with Name = name }, fileProps) 321 | | FolderNode(props, children) -> FolderNode({ props with Name = name }, children) 322 | fileTree.AddOrUpdateNode newFileName newNode 323 | 324 | FileSystemBase.STATUS_SUCCESS 325 | | None -> 326 | FileSystemBase.STATUS_OBJECT_PATH_NOT_FOUND 327 | 328 | override this.Cleanup(_, fileDesc, _, flags) = 329 | let isFlagSet flag value = 330 | value &&& flag <> 0u 331 | 332 | let path : string = downcast fileDesc 333 | Logging.logVerbose className "Cleanup" path 334 | 335 | match fileTree.FindNode path with 336 | | Some node -> 337 | if isFlagSet FileSystemBase.CleanupDelete flags then 338 | fileTree.RemoveNode path 339 | else 340 | match node with 341 | | FileNode(props, fileProps) as node -> 342 | let props = if isFlagSet FileSystemBase.CleanupSetArchiveBit flags then 343 | { props with FileAttributes = props.FileAttributes ||| uint32 FileAttributes.Archive } 344 | else props 345 | 346 | let systemTime = uint64 (DateTime.UtcNow.ToFileTimeUtc()) 347 | let props = if isFlagSet FileSystemBase.CleanupSetChangeTime flags then 348 | { props with LastModifiedFileTimeUtc = systemTime } 349 | else props 350 | let props = if isFlagSet FileSystemBase.CleanupSetLastWriteTime flags then 351 | { props with LastModifiedFileTimeUtc = systemTime } 352 | else props 353 | // TODO: we are not checking the lastAccessTime 354 | 355 | if isFlagSet FileSystemBase.CleanupSetAllocationSize flags then 356 | let allocationUnit = uint64 (MEMFS_SECTOR_SIZE * MEMFS_SECTORS_PER_ALLOCATION_UNIT) 357 | let allocationSize = (fileProps.FileSize + allocationUnit - 1UL) / allocationUnit * allocationUnit 358 | match setFileSize (node, allocationSize, true) with 359 | | Ok(FileNode(_, fileProps)) -> 360 | fileTree.AddOrUpdateNode path (FileNode(props, fileProps)) 361 | | Ok(FolderNode _) -> 362 | assert false 363 | | Error err -> 364 | Logging.logError className "Cleanup" (sprintf "'%s' -> 0x%x" path err) 365 | else 366 | fileTree.AddOrUpdateNode path (FileNode(props, fileProps)) 367 | | FolderNode(props, children) -> 368 | let systemTime = uint64 (DateTime.UtcNow.ToFileTimeUtc()) 369 | let props = if isFlagSet FileSystemBase.CleanupSetChangeTime flags then 370 | { props with LastModifiedFileTimeUtc = systemTime } 371 | else props 372 | let props = if isFlagSet FileSystemBase.CleanupSetLastWriteTime flags then 373 | { props with LastModifiedFileTimeUtc = systemTime } 374 | else props 375 | let node = FolderNode(props, children) 376 | fileTree.AddOrUpdateNode path node 377 | | None -> () 378 | 379 | override this.Flush(_, fileDesc, fileInfo) = 380 | let path : string = downcast fileDesc 381 | Logging.logVerbose className "Flush" path 382 | 383 | let node = fileTree.FindNode path 384 | assert node.IsSome 385 | fileInfo <- (getFileInfo node.Value) 386 | 387 | FileSystemBase.STATUS_SUCCESS 388 | 389 | override this.SetBasicInfo(_, fileDesc, fileAttributes, creationTime, 390 | _, lastWriteTime, _, fileInfo) = 391 | let path : string = downcast fileDesc 392 | Logging.logVerbose className "SetBasicInfo" path 393 | 394 | match fileTree.FindNode path with 395 | | Some(FileNode(props, _)) 396 | | Some(FolderNode(props, _)) as node -> 397 | let props = { 398 | Name = props.Name 399 | CreatedFileTimeUtc = if creationTime <> 0UL then creationTime 400 | else props.CreatedFileTimeUtc 401 | LastModifiedFileTimeUtc = if lastWriteTime <> 0UL then lastWriteTime 402 | else props.LastModifiedFileTimeUtc 403 | FileAttributes = if fileAttributes <> uint32 -1 then fileAttributes else props.FileAttributes 404 | } 405 | let node = match node.Value with 406 | | FileNode(_, data) -> FileNode(props, data) 407 | | FolderNode(_, children) -> FolderNode(props, children) 408 | 409 | fileTree.AddOrUpdateNode path node 410 | fileInfo <- getFileInfo node 411 | FileSystemBase.STATUS_SUCCESS 412 | | None -> FileSystemBase.STATUS_INVALID_DEVICE_REQUEST 413 | 414 | (* ********************************************* * 415 | * MISC * 416 | * ********************************************* *) 417 | 418 | override this.Init(host) = 419 | let host = host :?> FileSystemHost 420 | host.FileSystemName <- fileSystemName 421 | host.SectorSize <- MEMFS_SECTOR_SIZE 422 | host.SectorsPerAllocationUnit <- MEMFS_SECTORS_PER_ALLOCATION_UNIT 423 | host.VolumeCreationTime <- uint64 (DateTime.Now.ToFileTimeUtc()) 424 | host.VolumeSerialNumber <- uint32 (host.VolumeCreationTime / (10000UL * 1000UL)); 425 | host.CaseSensitiveSearch <- false 426 | host.CasePreservedNames <- true; 427 | host.UnicodeOnDisk <- true; 428 | host.PostCleanupWhenModifiedOnly <- true; 429 | host.PassQueryDirectoryPattern <- true 430 | FileSystemBase.STATUS_SUCCESS; 431 | 432 | override this.ExceptionHandler(ex) = 433 | Logging.logError className "ExceptionHandler" (ex.ToString()) 434 | FileSystemBase.STATUS_UNEXPECTED_IO_ERROR 435 | 436 | override this.GetVolumeInfo(volumeInfo) = 437 | Logging.logVerbose className "GetVolumeInfo" "" 438 | 439 | volumeInfo.TotalSize <- maxFileNodes * uint64 maxFileSize; 440 | volumeInfo.FreeSize <- volumeInfo.TotalSize //(maxFileNodes - FileNodeMap.Count()) * (UInt64)MaxFileSize; 441 | FileSystemBase.STATUS_SUCCESS 442 | 443 | override this.CanDelete(_, fileDesc, _) = 444 | let path : string = downcast fileDesc 445 | Logging.logVerbose className "CanDelete" path 446 | 447 | match fileTree.FindNode path with 448 | | Some(FolderNode(_, children)) when children.Count > 0 -> 449 | FileSystemBase.STATUS_DIRECTORY_NOT_EMPTY 450 | | Some _ -> 451 | FileSystemBase.STATUS_SUCCESS 452 | | None -> 453 | FileSystemBase.STATUS_INVALID_DEVICE_REQUEST 454 | 455 | override this.GetSecurityByName(path, fileAttributes, _) = 456 | Logging.logVerbose className "GetSecurityByName" path 457 | 458 | match fileTree.FindNode path with 459 | | Some(FileNode(props, _)) 460 | | Some(FolderNode(props, _)) -> 461 | fileAttributes <- uint32 props.FileAttributes 462 | FileSystemBase.STATUS_SUCCESS 463 | | None -> 464 | FileSystemBase.STATUS_OBJECT_NAME_NOT_FOUND 465 | 466 | override this.Close(_, fileDesc) = 467 | let path : string = downcast fileDesc 468 | Logging.logVerbose className "Close" path 469 | -------------------------------------------------------------------------------- /fsmemfs/MemoryFileSystemService.fs: -------------------------------------------------------------------------------- 1 | namespace LowLevelDesign.FsMemFs 2 | 3 | open Fsp 4 | open System 5 | open System.Diagnostics 6 | open System.IO 7 | open System.Reflection 8 | 9 | exception CommandLineError of string 10 | 11 | type ServiceArgs = { 12 | MountPoint : string 13 | EnableVerboseLog : bool 14 | LogPath : string option 15 | DriverLogPath : string option 16 | FileSystemName : string 17 | } 18 | 19 | type MemoryFileSystemService() = 20 | inherit Service("FsMemFsService") 21 | 22 | let className = typedefof.Name 23 | let logger = TraceSource(className) 24 | 25 | let mutable host : FileSystemHost = null 26 | 27 | let parseArgs (args : string array) = 28 | let args = args |> Array.collect (fun a -> a.Split()) |> List.ofArray 29 | 30 | let rec parseArg (lastArg : string) (args : string list) = 31 | let isArg (a : string) = a.StartsWith("-", StringComparison.Ordinal) 32 | let extractArgName (a : string) = a.TrimStart('-') 33 | 34 | match args with 35 | | [] -> 36 | Map.empty 37 | | a :: rest when isArg a && lastArg = "" -> 38 | parseArg (extractArgName a) rest 39 | | a :: rest when isArg a -> 40 | let m = parseArg (extractArgName a) rest 41 | m |> Map.add lastArg None 42 | | a :: rest -> 43 | let m = parseArg "" rest 44 | m |> Map.add lastArg (Some a) 45 | 46 | parseArg "" args 47 | 48 | let parseAndValidate args = 49 | let getArgVal (args : Map) key = 50 | if not (args.ContainsKey(key)) || args.[key].IsNone then None 51 | else args.[key] 52 | 53 | let parsedArgs = parseArgs args 54 | if (getArgVal parsedArgs "m").IsNone then 55 | raise (CommandLineError "Mountpoint (-m) is missing.") 56 | 57 | { 58 | MountPoint = parsedArgs.["m"].Value 59 | EnableVerboseLog = parsedArgs.ContainsKey("v") 60 | LogPath = getArgVal parsedArgs "l" 61 | DriverLogPath = getArgVal parsedArgs "D" 62 | FileSystemName = match getArgVal parsedArgs "F" with 63 | | Some name -> name 64 | | None -> "fsmemfs" 65 | } 66 | 67 | override this.OnStart(args : string array) = 68 | try 69 | let msg = sprintf "Starting service, args: %A" args 70 | logger.TraceInformation(msg) 71 | 72 | let args = parseAndValidate args 73 | let debugFlags = if args.EnableVerboseLog then uint32 -1 else 0u 74 | let switch = if args.EnableVerboseLog then SourceSwitch("default", Level = SourceLevels.Verbose) 75 | else SourceSwitch("default", Level = SourceLevels.Information) 76 | Logging.Logger.Switch <- switch 77 | 78 | if args.LogPath.IsSome then 79 | Logging.Logger.Listeners.Add(new TextWriterTraceListener(args.LogPath.Value)) |> ignore 80 | 81 | if args.DriverLogPath.IsSome then 82 | if FileSystemHost.SetDebugLogFile(args.DriverLogPath.Value) < 0 then 83 | raise (CommandLineError "Cannot open driver log file.") 84 | 85 | host <- new FileSystemHost(MemoryFileSystem(args.FileSystemName)) 86 | 87 | if host.Mount(args.MountPoint, null, true, debugFlags) < 0 then 88 | raise (IOException("Mounting file system failed")) 89 | with 90 | | CommandLineError err -> 91 | Service.Log(Service.EVENTLOG_ERROR_TYPE, (sprintf "fsmemfs: %s" err)) 92 | Service.Log(Service.EVENTLOG_INFORMATION_TYPE, 93 | sprintf "F# Memory File System v%s" 94 | (Assembly.GetExecutingAssembly().GetName().Version.ToString())) 95 | 96 | let message = """ 97 | Copyright (C) 2019 Sebastian Solnica (lowleveldesign.org) 98 | 99 | Fsmemfs uses WinFsp (WinFsp - Windows File System Proxy, Copyright (C) Bill Zissimopoulos) to mount 100 | the user-mode file system. You need to have it installed in order to run and build fsmemfs. 101 | Check the WinFsp GitHub page (https://github.com/billziss-gh/winfsp) to learn more and download the installer. 102 | 103 | Usage: fsmemfs OPTIONS 104 | 105 | Options: 106 | -m VALUE [required] mount point, could be a drive (G:) or a folder (C:\memfs) 107 | -v [optional] enable verbose log 108 | -l VALUE [optional] a path to a file where fsmemfs should write logs 109 | -D VALUE [optional] a path to a file where WinFsp should write debug logs 110 | -F VALUE [optional] a filesystem name, if not set it will be 'fsmemfs' 111 | """ 112 | Service.Log(Service.EVENTLOG_ERROR_TYPE, message) 113 | reraise() 114 | 115 | interface IDisposable with 116 | member this.Dispose() = 117 | if host <> null then 118 | host.Dispose() 119 | -------------------------------------------------------------------------------- /fsmemfs/Program.fs: -------------------------------------------------------------------------------- 1 | module LowLevelDesign.FsMemFs.Program 2 | 3 | [] 4 | let main _ = 5 | use service = new MemoryFileSystemService() 6 | service.Run() 7 | -------------------------------------------------------------------------------- /fsmemfs/fsmemfs.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {E532858D-56AD-42C3-A9A4-1C2B1D5D1AFD} 9 | Exe 10 | LowLevelDesign.FsMemFs 11 | fsmemfs 12 | v4.6.2 13 | true 14 | bin\$(Configuration)\$(AssemblyName).xml 15 | 16 | 17 | true 18 | portable 19 | false 20 | false 21 | bin\$(Configuration)\ 22 | DEBUG;TRACE 23 | 3 24 | --warnon:1182 25 | 26 | 27 | pdbonly 28 | true 29 | true 30 | bin\$(Configuration)\ 31 | TRACE 32 | 3 33 | --warnon:1182 34 | 35 | 36 | 37 | ..\packages\FSharp.Core.4.6.2\lib\net45\FSharp.Core.dll 38 | True 39 | 40 | 41 | 42 | 43 | 44 | 45 | ..\..\..\..\Program Files (x86)\WinFsp\bin\winfsp-msil.dll 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /fsmemfs/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | --------------------------------------------------------------------------------