├── .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 |
--------------------------------------------------------------------------------