summaryrefslogtreecommitdiff
path: root/src/main/kotlin/io/files.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/io/files.kt')
-rw-r--r--src/main/kotlin/io/files.kt203
1 files changed, 203 insertions, 0 deletions
diff --git a/src/main/kotlin/io/files.kt b/src/main/kotlin/io/files.kt
new file mode 100644
index 0000000..4c53c4e
--- /dev/null
+++ b/src/main/kotlin/io/files.kt
@@ -0,0 +1,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
+ }
+
+}