-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFilesystemModel.lean
More file actions
248 lines (212 loc) · 7.41 KB
/
Copy pathFilesystemModel.lean
File metadata and controls
248 lines (212 loc) · 7.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/- Valence Shell - Filesystem Model (Lean 4)
A formal model of POSIX-like filesystem operations for proving
MAA (Mutually Assured Accountability) properties.
This model focuses on directory operations (mkdir/rmdir) with
the goal of proving reversibility and correctness properties.
-/
-- Path Model
abbrev PathComponent := String
abbrev Path := List PathComponent
def rootPath : Path := []
def parentPath (p : Path) : Path :=
match p.reverse with
| [] => []
| _ :: rest => rest.reverse
def pathName (p : Path) : Option PathComponent :=
match p.reverse with
| [] => none
| name :: _ => some name
-- Filesystem Nodes
inductive FSNodeType where
| file : FSNodeType
| directory : FSNodeType
deriving DecidableEq, Repr
structure Permissions where
readable : Bool
writable : Bool
executable : Bool
deriving DecidableEq, Repr
def defaultPerms : Permissions := ⟨true, true, true⟩
structure FSNode where
nodeType : FSNodeType
permissions : Permissions
deriving DecidableEq, Repr
-- Filesystem State
abbrev Filesystem := Path → Option FSNode
def emptyFS : Filesystem :=
fun p => match p with
| [] => some ⟨FSNodeType.directory, defaultPerms⟩
| _ => none
-- Filesystem Predicates
def pathExists (p : Path) (fs : Filesystem) : Prop :=
∃ node, fs p = some node
def isDirectory (p : Path) (fs : Filesystem) : Prop :=
∃ perms, fs p = some ⟨FSNodeType.directory, perms⟩
def isFile (p : Path) (fs : Filesystem) : Prop :=
∃ perms, fs p = some ⟨FSNodeType.file, perms⟩
def parentExists (p : Path) (fs : Filesystem) : Prop :=
pathExists (parentPath p) fs
def hasWritePermission (p : Path) (fs : Filesystem) : Prop :=
∃ node, fs p = some node ∧ node.permissions.writable = true
def isEmptyDir (p : Path) (fs : Filesystem) : Prop :=
isDirectory p fs ∧
∀ child : Path, child.isPrefixOf p → child ≠ p → ¬pathExists child fs
-- Basic Lemmas
-- A non-empty path is never equal to its parent
theorem nonempty_path_ne_parent (p : Path) (h : p ≠ []) :
p ≠ parentPath p := by
intro heq
unfold parentPath at heq
match hp : p.reverse with
| [] =>
-- If p.reverse = [], then p = []
have : p = [] := by
cases p with
| nil => rfl
| cons x xs => simp at hp
contradiction
| x :: rest =>
-- p.reverse = x :: rest, so parentPath p = rest.reverse
simp only [hp] at heq
-- heq says p = rest.reverse, but p.reverse = x :: rest
-- So (rest.reverse).reverse = x :: rest
-- Which means rest = x :: rest (after reverse cancellation), impossible
have hp2 : p.reverse = x :: rest := hp
rw [heq] at hp2
-- `simp` derives `rest = x :: rest` in `hp2`, which is impossible, so it
-- closes the goal on its own; a trailing `contradiction` is dead here
-- (Lean 4.12 rejects it as "no goals to be solved").
simp at hp2
theorem pathExists_emptyFS_root :
pathExists rootPath emptyFS := by
unfold pathExists emptyFS
exists ⟨FSNodeType.directory, defaultPerms⟩
theorem not_pathExists_emptyFS (p : Path) (h : p ≠ rootPath) :
¬pathExists p emptyFS := by
unfold pathExists emptyFS
intro ⟨node, heq⟩
cases p with
| nil => contradiction
| cons _ _ => contradiction
-- Filesystem Operations
def fsUpdate (p : Path) (n : Option FSNode) (fs : Filesystem) : Filesystem :=
fun p' => if p = p' then n else fs p'
-- mkdir: Create a directory
structure MkdirPrecondition (p : Path) (fs : Filesystem) : Prop where
notExists : ¬pathExists p fs
parentExists : parentExists p fs
parentIsDir : isDirectory (parentPath p) fs
parentWritable : hasWritePermission (parentPath p) fs
def mkdir (p : Path) (fs : Filesystem) : Filesystem :=
fsUpdate p (some ⟨FSNodeType.directory, defaultPerms⟩) fs
-- rmdir: Remove a directory
structure RmdirPrecondition (p : Path) (fs : Filesystem) : Prop where
isDir : isDirectory p fs
isEmpty : isEmptyDir p fs
parentWritable : hasWritePermission (parentPath p) fs
notRoot : p ≠ rootPath
def rmdir (p : Path) (fs : Filesystem) : Filesystem :=
fsUpdate p none fs
-- Postcondition Theorems
theorem mkdir_creates_directory (p : Path) (fs : Filesystem)
(hpre : MkdirPrecondition p fs) :
isDirectory p (mkdir p fs) := by
unfold isDirectory mkdir fsUpdate
exists defaultPerms
simp
theorem mkdir_path_exists (p : Path) (fs : Filesystem)
(hpre : MkdirPrecondition p fs) :
pathExists p (mkdir p fs) := by
unfold pathExists mkdir fsUpdate
exists ⟨FSNodeType.directory, defaultPerms⟩
simp
theorem rmdir_removes_path (p : Path) (fs : Filesystem)
(hpre : RmdirPrecondition p fs) :
¬pathExists p (rmdir p fs) := by
unfold pathExists rmdir fsUpdate
intro ⟨node, heq⟩
simp at heq
-- The Main Reversibility Theorem
theorem mkdir_rmdir_reversible (p : Path) (fs : Filesystem)
(hpre : MkdirPrecondition p fs) :
rmdir p (mkdir p fs) = fs := by
unfold rmdir mkdir fsUpdate
funext p'
by_cases h : p = p'
· -- p = p'
subst h
simp
-- Need to show: none = fs p
-- We know hpre.notExists : ¬pathExists p fs
-- pathExists p fs = ∃ node, fs p = some node
-- So if fs p = some node, we'd have a contradiction
cases hfs : fs p with
| none => rfl
| some node =>
exfalso
apply hpre.notExists
unfold pathExists
exact ⟨node, hfs⟩
· -- p ≠ p'
simp [h]
-- The Reverse Direction: rmdir then mkdir
-- Note: Requires original directory had defaultPerms for exact equality
theorem rmdir_mkdir_reversible (p : Path) (fs : Filesystem)
(hpre : RmdirPrecondition p fs)
(hperms : fs p = some ⟨FSNodeType.directory, defaultPerms⟩) :
mkdir p (rmdir p fs) = fs := by
unfold mkdir rmdir fsUpdate
funext p'
by_cases h : p = p'
· -- p = p'
subst h
simp
-- After rmdir, fs p = none, then mkdir recreates it
-- We need to show: some ⟨directory, defaultPerms⟩ = fs p
-- By assumption, fs p = some ⟨directory, defaultPerms⟩
rw [hperms]
· -- p ≠ p'
simp [h]
-- Additional Theorems
theorem mkdir_preserves_other_paths (p p' : Path) (fs : Filesystem)
(hneq : p ≠ p') :
fs p' = (mkdir p fs) p' := by
unfold mkdir fsUpdate
simp [hneq]
theorem rmdir_preserves_other_paths (p p' : Path) (fs : Filesystem)
(hneq : p ≠ p') :
fs p' = (rmdir p fs) p' := by
unfold rmdir fsUpdate
simp [hneq]
-- Helper: If MkdirPrecondition holds, the path is not root
-- Because root always exists in any reasonable filesystem
theorem mkdir_precondition_path_nonempty (p : Path) (fs : Filesystem)
(hpre : MkdirPrecondition p fs)
(hroot_exists : pathExists [] fs) :
p ≠ [] := by
intro heq
subst heq
exact hpre.notExists hroot_exists
theorem mkdir_parent_still_exists (p : Path) (fs : Filesystem)
(hpre : MkdirPrecondition p fs) :
pathExists (parentPath p) (mkdir p fs) := by
unfold pathExists
cases hpre.parentExists with
| intro node hnode =>
exists node
unfold mkdir fsUpdate
by_cases h : p = parentPath p
· -- This case is impossible for non-root paths
exfalso
by_cases hp : p = []
· -- If p = [], then parentPath [] = []
-- hpre.parentExists : pathExists [] fs
-- hpre.notExists : ¬pathExists [] fs
-- Contradiction!
subst hp
have hpe : pathExists [] fs := hpre.parentExists
exact hpre.notExists hpe
· -- If p ≠ [], then by nonempty_path_ne_parent, p ≠ parentPath p
exact nonempty_path_ne_parent p hp h
· simp [h]
exact hnode