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
|
package io
import User
sealed interface CreateFileResult {
object Created : CreateFileResult
sealed interface Failure : CreateFileResult {
object NoPermission : Failure
object NoParent : Failure
object AlreadyExists : Failure
}
}
sealed interface WriteFileResult {
object Written : WriteFileResult
sealed interface Failure : WriteFileResult {
object NoPermission : Failure
object NotAFile : Failure
data class NotEnoughSpace(
val dataSize: Long,
val spaceLeft: Long
) : Failure
}
}
sealed interface ReadFileResult {
data class Read(val data: ByteArray) : ReadFileResult {
override fun equals(other: Any?): Boolean = (other as? Read)?.let { it.data.contentEquals(this.data) } ?: false
override fun hashCode(): Int {
return data.contentHashCode()
}
}
sealed interface Failure : ReadFileResult {
object NoPermission : Failure
object NotAFile : Failure
}
}
sealed interface DeleteFileResult {
sealed interface Failure : DeleteFileResult {
object NoPermission : Failure
object NotAFile : Failure
}
object Deleted : DeleteFileResult
}
interface FileService<INode> {
fun getPath(iNode: INode): Path.Absolute
fun getINode(path: Path.Absolute): INode
fun createFile(iNode: INode, user: User): CreateFileResult
fun createSymlink(iNode: INode, user: User, path: Path): CreateFileResult
fun createDirectory(iNode: INode, user: User): CreateFileResult
fun writeToFile(iNode: INode, user: User, data: ByteArray): WriteFileResult
fun readFromFile(iNode: INode, user: User): ReadFileResult
fun deleteFile(iNode: INode, user: User): DeleteFileResult
fun exists(iNode: INode): Boolean
fun isFile(iNode: INode): Boolean
fun isDirectory(iNode: INode): Boolean
fun isSymlink(iNode: INode): Boolean
fun resolve(iNode: INode, fragment: String): INode
fun resolve(iNode: INode, path: Path.Relative): INode
fun changePermissions(iNode: INode, user: User, permissionUpdate: Map<User, Permission>)
}
data class Permission(
val read: Boolean,
val write: Boolean,
val execute: Boolean
) {
companion object {
val default get() = Permission(read = true, write = true, execute = false)
}
}
sealed interface PrimitiveStorageBlob {
val permissions: MutableMap<User, Permission>
class File(var data: ByteArray, override val permissions: MutableMap<User, Permission>) : PrimitiveStorageBlob
class Symlink(val path: Path, override val permissions: MutableMap<User, Permission>) : PrimitiveStorageBlob
class Directory(override val permissions: MutableMap<User, Permission>) : PrimitiveStorageBlob
}
data class PrimitiveINode internal constructor(internal val internalPath: String)
class PrimitiveFileService : FileService<PrimitiveINode> {
private val storageBlobs = mutableMapOf<String, PrimitiveStorageBlob>(
"/" to PrimitiveStorageBlob.Directory(mutableMapOf())
)
override fun getPath(iNode: PrimitiveINode): Path.Absolute = Path.of(iNode.internalPath) as Path.Absolute
override fun getINode(path: Path.Absolute): PrimitiveINode {
return resolve(PrimitiveINode("/"), Path.Relative(path.parts))
}
override fun resolve(iNode: PrimitiveINode, fragment: String): PrimitiveINode {
if (fragment == "..") {
val up = iNode.internalPath.substringBeforeLast('/')
if (up.isEmpty()) return PrimitiveINode("/")
return PrimitiveINode(up)
}
if (fragment.isEmpty() || fragment == ".")
return iNode
val blob = storageBlobs[iNode.internalPath]
return when (blob) {
is PrimitiveStorageBlob.Symlink -> {
when (blob.path) {
is Path.Absolute -> getINode(blob.path)
is Path.Relative -> resolve(resolve(iNode, ".."), blob.path)
}
}
else -> {
PrimitiveINode(iNode.internalPath + "/" + fragment)
}
}
}
override fun resolve(iNode: PrimitiveINode, path: Path.Relative): PrimitiveINode =
path.parts.fold(iNode) { node, fragment -> resolve(node, fragment) }
private fun getStorageBlob(iNode: PrimitiveINode): PrimitiveStorageBlob? = storageBlobs[iNode.internalPath]
override fun writeToFile(iNode: PrimitiveINode, user: User, data: ByteArray): WriteFileResult {
val file = getStorageBlob(iNode) as? PrimitiveStorageBlob.File ?: return WriteFileResult.Failure.NotAFile
if (!hasPermission(user, file) { read })
return WriteFileResult.Failure.NoPermission
file.data = data
return WriteFileResult.Written
}
override fun readFromFile(iNode: PrimitiveINode, user: User): ReadFileResult {
val file = getStorageBlob(iNode) as? PrimitiveStorageBlob.File ?: return ReadFileResult.Failure.NotAFile
if (!hasPermission(user, file) { read })
return ReadFileResult.Failure.NoPermission
return ReadFileResult.Read(file.data)
}
override fun exists(iNode: PrimitiveINode): Boolean =
getStorageBlob(iNode) != null
override fun isFile(iNode: PrimitiveINode): Boolean =
getStorageBlob(iNode) is PrimitiveStorageBlob.File
override fun isDirectory(iNode: PrimitiveINode): Boolean =
getStorageBlob(iNode) is PrimitiveStorageBlob.Directory
override fun isSymlink(iNode: PrimitiveINode): Boolean =
getStorageBlob(iNode) is PrimitiveStorageBlob.Symlink
override fun changePermissions(iNode: PrimitiveINode, user: User, permissionUpdate: Map<User, Permission>) {
val file = getStorageBlob(iNode) ?: return // TODO Results
if (!hasPermission(user, file) { write })
return // TODO Results
file.permissions.putAll(permissionUpdate)
return // TODO Results
}
override fun deleteFile(iNode: PrimitiveINode, user: User): DeleteFileResult {
val file = getStorageBlob(iNode) ?: return DeleteFileResult.Failure.NotAFile
if (!hasPermission(user, file) { write })
return DeleteFileResult.Failure.NoPermission
(storageBlobs.keys.filter { it.startsWith(iNode.internalPath + "/") } + listOf(iNode.internalPath)).forEach {
storageBlobs.remove(it)
}
return DeleteFileResult.Deleted
}
private fun hasPermission(user: User, blob: PrimitiveStorageBlob, check: Permission.() -> Boolean): Boolean {
return user.isRoot || blob.permissions[user]?.let(check) ?: false
}
private fun checkCreationPreconditions(iNode: PrimitiveINode, user: User): CreateFileResult? {
if (storageBlobs.containsKey(iNode.internalPath)) return CreateFileResult.Failure.AlreadyExists
val parent = getStorageBlob(resolve(iNode, ".."))
if (parent !is PrimitiveStorageBlob.Directory) return CreateFileResult.Failure.NoParent
if (!hasPermission(user, parent) { write }) return CreateFileResult.Failure.NoPermission
return null
}
override fun createFile(iNode: PrimitiveINode, user: User): CreateFileResult {
val preconditions = checkCreationPreconditions(iNode, user)
if (preconditions != null) return preconditions
storageBlobs[iNode.internalPath] = PrimitiveStorageBlob.File(byteArrayOf(), mutableMapOf(user to Permission.default))
return CreateFileResult.Created
}
override fun createSymlink(iNode: PrimitiveINode, user: User, path: Path): CreateFileResult {
val preconditions = checkCreationPreconditions(iNode, user)
if (preconditions != null) return preconditions
storageBlobs[iNode.internalPath] = PrimitiveStorageBlob.Symlink(path, mutableMapOf(user to Permission.default))
return CreateFileResult.Created
}
override fun createDirectory(iNode: PrimitiveINode, user: User): CreateFileResult {
val preconditions = checkCreationPreconditions(iNode, user)
if (preconditions != null) return preconditions
storageBlobs[iNode.internalPath] = PrimitiveStorageBlob.Directory(mutableMapOf(user to Permission.default))
return CreateFileResult.Created
}
}
|