diff --git a/.idea/libraries/TerranVirtualDisk.xml b/.idea/libraries/TerranVirtualDisk.xml
index 2e09a48..e0e61d4 100644
--- a/.idea/libraries/TerranVirtualDisk.xml
+++ b/.idea/libraries/TerranVirtualDisk.xml
@@ -4,6 +4,8 @@
-
+
+
+
\ No newline at end of file
diff --git a/lib/TerranVirtualDisk-src.jar b/lib/TerranVirtualDisk-src.jar
index 962c2af..7626feb 100644
Binary files a/lib/TerranVirtualDisk-src.jar and b/lib/TerranVirtualDisk-src.jar differ
diff --git a/lib/TerranVirtualDisk.jar b/lib/TerranVirtualDisk.jar
index edb7db7..39a526d 100644
Binary files a/lib/TerranVirtualDisk.jar and b/lib/TerranVirtualDisk.jar differ
diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt
index a00ff39..b246b7f 100644
--- a/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt
+++ b/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt
@@ -462,10 +462,10 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath:
private fun getSizeStr(): String {
val sb = StringBuilder()
- val isRoot = (file.absolutePath == rootPath.absolutePath)
+// val isRoot = (file.absolutePath == rootPath.absolutePath)
if (file.isFile) sb.append(file.length())
- else sb.append(file.listFiles().size)
+ else sb.append(file.listFiles()!!.size)
return sb.toString()
}
diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/TevdDiskDrive.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/TevdDiskDrive.kt
index ca6d8bc..9c1b250 100644
--- a/tsvm_core/src/net/torvald/tsvm/peripheral/TevdDiskDrive.kt
+++ b/tsvm_core/src/net/torvald/tsvm/peripheral/TevdDiskDrive.kt
@@ -2,28 +2,30 @@ package net.torvald.tsvm.peripheral
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil
import net.torvald.tsvm.VM
-import java.io.ByteArrayOutputStream
-import java.io.File
-import java.io.FileNotFoundException
+import java.io.*
+import java.util.*
/**
* Created by minjaesong on 2022-12-15.
*/
-class TevdDiskDrive(private val vm: VM, private val theRootPath: String) : BlockTransferInterface(false, true) {
+class TevdDiskDrive(private val vm: VM, private val theTevdPath: String, val diskUUIDstr: String) : BlockTransferInterface(false, true) {
private val DBGPRN = true
+ val diskID: UUID = UUID.fromString(diskUUIDstr)
+
private fun printdbg(msg: Any) {
- if (DBGPRN) println("[TestDiskDrive] $msg")
+ if (DBGPRN) println("[TevDiskDrive] $msg")
}
- private val rootPath = File(theRootPath)
+ private val tevdPath = File(theTevdPath)
+ private val DOM = VDUtil.readDiskArchive(tevdPath, charset = VM.CHARSET)
private var fileOpen = false
private var fileOpenMode = -1 // 1: 'W", 2: 'A'
- private var file = File(rootPath.toURI())
+ private var file: TevdFileDescriptor = TevdFileDescriptor(DOM, "")
//private var readModeLength = -1 // always 4096
private var writeMode = false
private var appendMode = false
@@ -37,12 +39,11 @@ class TevdDiskDrive(private val vm: VM, private val theRootPath: String) : Block
init {
statusCode = TestDiskDrive.STATE_CODE_STANDBY
- if (!rootPath.exists()) {
- throw FileNotFoundException("Disk file '${theRootPath}' not found")
+ if (!tevdPath.exists()) {
+ throw FileNotFoundException("Disk file '${theTevdPath}' not found")
}
}
- private val DOM = VDUtil.readDiskArchive(rootPath, charset = VM.CHARSET)
companion object {
/** How often the changes in DOM (disk object model) should be saved to the physical drive when there are changes. Seconds. */
@@ -50,7 +51,7 @@ class TevdDiskDrive(private val vm: VM, private val theRootPath: String) : Block
}
fun commit() {
- VDUtil.dumpToRealMachine(DOM, rootPath)
+ VDUtil.dumpToRealMachine(DOM, tevdPath)
}
@@ -91,9 +92,378 @@ class TevdDiskDrive(private val vm: VM, private val theRootPath: String) : Block
private lateinit var writeBuffer: ByteArray
private var writeBufferUsage = 0
+ /** Computer's attempt to startSend() will result in calling this very function.
+ * In such cases, `inputData` will be the message the computer sends.
+ *
+ * Disk drive must create desired side effects in accordance with the input message.
+ */
override fun writeoutImpl(inputData: ByteArray) {
- TODO("Not yet implemented")
+ if (writeMode || appendMode) {
+ //println("[DiskDrive] writeout with inputdata length of ${inputData.size}")
+ //println("[DiskDriveMsg] ${inputData.toString(Charsets.UTF_8)}")
+
+ if (!fileOpen) throw InternalError("File is not open but the drive is in write mode")
+
+ System.arraycopy(inputData, 0, writeBuffer, writeBufferUsage, minOf(writeModeLength - writeBufferUsage, inputData.size, BLOCK_SIZE))
+ writeBufferUsage += inputData.size
+
+ if (writeBufferUsage >= writeModeLength) {
+ // commit to the disk
+ if (appendMode)
+ file.appendBytes(writeBuffer)
+ else if (writeMode)
+ file.writeBytes(writeBuffer)
+
+ writeMode = false
+ appendMode = false
+ }
+ }
+ else {
+ val inputString = inputData.trimNull().toString(VM.CHARSET)
+
+ if (inputString.startsWith("DEVRST\u0017")) {
+ printdbg("Device Reset")
+ //readModeLength = -1
+ fileOpen = false
+ fileOpenMode = -1
+ file = TevdFileDescriptor(DOM, "")
+ blockSendCount = 0
+ statusCode = TestDiskDrive.STATE_CODE_STANDBY
+ writeMode = false
+ writeModeLength = -1
+ }
+ else if (inputString.startsWith("DEVSTU\u0017"))
+ recipient?.writeout(
+ TestDiskDrive.composePositiveAns(
+ "${statusCode.toChar()}",
+ TestDiskDrive.errorMsgs[statusCode]
+ )
+ )
+ else if (inputString.startsWith("DEVTYP\u0017"))
+ recipient?.writeout(TestDiskDrive.composePositiveAns("STOR"))
+ else if (inputString.startsWith("DEVNAM\u0017"))
+ recipient?.writeout(TestDiskDrive.composePositiveAns("Testtec Virtual Disk Drive"))
+ else if (inputString.startsWith("OPENR\"") || inputString.startsWith("OPENW\"") || inputString.startsWith("OPENA\"")) {
+ if (fileOpen) {
+
+ statusCode = TestDiskDrive.STATE_CODE_FILE_ALREADY_OPENED
+ return
+ }
+
+ printdbg("msg: $inputString, lastIndex: ${inputString.lastIndex}")
+
+ val openMode = inputString[4]
+ printdbg("open mode: $openMode")
+ // split inputstring into path and optional drive-number
+
+ // get position of latest delimeter (comma)
+ var commaIndex = inputString.lastIndex
+ while (commaIndex > 6) {
+ if (inputString[commaIndex] == ',') break; commaIndex -= 1
+ }
+ // sanity check if path is actually enclosed with double-quote
+ if (commaIndex != 6 && inputString[commaIndex - 1] != '"') {
+ statusCode = TestDiskDrive.STATE_CODE_ILLEGAL_COMMAND
+ return
+ }
+ val pathStr = inputString.substring(6, if (commaIndex == 6) inputString.lastIndex else commaIndex - 1)
+ val driveNum =
+ if (commaIndex == 6) null else inputString.substring(commaIndex + 1, inputString.length).toInt()
+ val filePath = filterSuperRoot(sanitisePath(pathStr))
+
+ // TODO driveNum is for disk drives that may have two or more slots built; for testing purposes we'll ignore it
+
+ file = TevdFileDescriptor(DOM, filePath)
+ printdbg("file path: ${file.canonicalPath}, drive num: $driveNum")
+
+ if (openMode == 'R' && !file.exists()) {
+ printdbg("! file not found")
+ statusCode = TestDiskDrive.STATE_CODE_NO_SUCH_FILE_EXISTS
+ return
+ }
+
+ statusCode = TestDiskDrive.STATE_CODE_STANDBY
+ fileOpen = true
+ fileOpenMode = when (openMode) {
+ 'W' -> 1
+ 'A' -> 2
+ else -> -1
+ }
+ blockSendCount = 0
+ }
+ else if (inputString.startsWith("DELETE")) {
+ if (!fileOpen) {
+ statusCode = TestDiskDrive.STATE_CODE_NO_FILE_OPENED
+ return
+ }
+ try {
+ file.delete()
+ }
+ catch (e: SecurityException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_SECURITY_ERROR
+ return
+ }
+ catch (e1: IOException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_IO_ERROR
+ return
+ }
+ }
+ else if (inputString.startsWith("LISTFILES")) {
+ // TODO temporary behaviour to ignore any arguments
+ resetBuf()
+ if (!fileOpen) {
+ statusCode = TestDiskDrive.STATE_CODE_NO_FILE_OPENED
+ return
+ }
+ try {
+ if (file.isDirectory) {
+ file.listFiles()!!.forEachIndexed { index, lsfile ->
+ if (index != 0) messageComposeBuffer.write(0x1E)
+ messageComposeBuffer.write(if (lsfile.isDirectory) 0x11 else 0x12)
+ messageComposeBuffer.write(lsfile.name.toByteArray(VM.CHARSET))
+ }
+
+ statusCode = TestDiskDrive.STATE_CODE_STANDBY
+ }
+ else {
+ statusCode = TestDiskDrive.STATE_CODE_NOT_A_DIRECTORY
+ return
+ }
+ }
+ catch (e: SecurityException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_SECURITY_ERROR
+ return
+ }
+ catch (e1: IOException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_IO_ERROR
+ return
+ }
+ }
+ else if (inputString.startsWith("GETLEN")) {
+ // TODO temporary behaviour to ignore any arguments
+ resetBuf()
+ if (!fileOpen) {
+ statusCode = TestDiskDrive.STATE_CODE_NO_FILE_OPENED
+ return
+ }
+
+ messageComposeBuffer.write(getSizeStr().toByteArray(VM.CHARSET))
+ statusCode = TestDiskDrive.STATE_CODE_STANDBY
+ }
+ else if (inputString.startsWith("LIST")) {
+ // TODO temporary behaviour to ignore any arguments
+ resetBuf()
+ if (!fileOpen) {
+ statusCode = TestDiskDrive.STATE_CODE_NO_FILE_OPENED
+ return
+ }
+
+ messageComposeBuffer.write(getReadableLs().toByteArray(VM.CHARSET))
+ statusCode = TestDiskDrive.STATE_CODE_STANDBY
+ }
+ else if (inputString.startsWith("CLOSE")) {
+ fileOpen = false
+ fileOpenMode = -1
+ statusCode = TestDiskDrive.STATE_CODE_STANDBY
+ }
+ else if (inputString.startsWith("READ")) {
+ //readModeLength = inputString.substring(4 until inputString.length).toInt()
+
+ resetBuf()
+ if (file.isFile) {
+ try {
+ messageComposeBuffer.write(file.readBytes())
+ statusCode = TestDiskDrive.STATE_CODE_STANDBY
+ }
+ catch (e: IOException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_IO_ERROR
+ }
+ }
+ else {
+ statusCode = TestDiskDrive.STATE_CODE_OPERATION_NOT_PERMITTED
+ return
+ }
+ }
+ else if (inputString.startsWith("LOADBOOT")) {
+ var commaIndex = 0
+ while (commaIndex < inputString.length) {
+ if (inputString[commaIndex] == ',') break
+ commaIndex += 1
+ }
+ val driveNum = if (commaIndex >= inputString.length) null else commaIndex
+
+ // TODO driveNum is for disk drives that may have two or more slots built; for testing purposes we'll ignore it
+
+ val bootFile = TevdFileDescriptor(DOM, "!BOOTSEC")
+
+ if (!bootFile.exists()) {
+ statusCode = TestDiskDrive.STATE_CODE_NO_SUCH_FILE_EXISTS
+ return
+ }
+ try {
+ val retMsg = bootFile.getHeadBytes(BLOCK_SIZE)
+ recipient?.writeout(retMsg)
+ statusCode = TestDiskDrive.STATE_CODE_STANDBY
+ }
+ catch (e: IOException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_IO_ERROR
+ return
+ }
+ }
+ else if (inputString.startsWith("MKDIR")) {
+ if (!fileOpen) {
+ statusCode = TestDiskDrive.STATE_CODE_NO_FILE_OPENED
+ return
+ }
+ if (fileOpenMode < 1) {
+ statusCode = TestDiskDrive.STATE_CODE_OPERATION_NOT_PERMITTED
+ return
+ }
+ try {
+ val status = file.mkdir()
+ statusCode = if (status) 0 else 1
+ }
+ catch (e: SecurityException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_SECURITY_ERROR
+ }
+ }
+ else if (inputString.startsWith("MKFILE")) {
+ if (!fileOpen) {
+ statusCode = TestDiskDrive.STATE_CODE_NO_FILE_OPENED
+ return
+ }
+ if (fileOpenMode < 1) {
+ statusCode = TestDiskDrive.STATE_CODE_OPERATION_NOT_PERMITTED
+ return
+ }
+ try {
+ val f1 = file.createNewFile()
+ statusCode = if (f1) TestDiskDrive.STATE_CODE_STANDBY else TestDiskDrive.STATE_CODE_OPERATION_FAILED
+ return
+ }
+ catch (e: IOException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_IO_ERROR
+ }
+ catch (e1: SecurityException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_SECURITY_ERROR
+ }
+ }
+ else if (inputString.startsWith("TOUCH")) {
+ if (!fileOpen) {
+ statusCode = TestDiskDrive.STATE_CODE_NO_FILE_OPENED
+ return
+ }
+ if (fileOpenMode < 1) {
+ statusCode = TestDiskDrive.STATE_CODE_OPERATION_NOT_PERMITTED
+ return
+ }
+ try {
+ val f1 = file.setLastModified(vm.worldInterface.currentTimeInMills())
+ statusCode = if (f1) TestDiskDrive.STATE_CODE_STANDBY else TestDiskDrive.STATE_CODE_OPERATION_FAILED
+ return
+ }
+ catch (e: IOException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_IO_ERROR
+ }
+ catch (e1: SecurityException) {
+ statusCode = TestDiskDrive.STATE_CODE_SYSTEM_SECURITY_ERROR
+ }
+ }
+ else if (inputString.startsWith("WRITE")) {
+ if (!fileOpen) {
+ statusCode = TestDiskDrive.STATE_CODE_NO_FILE_OPENED
+ return
+ }
+ if (fileOpenMode < 0) {
+ statusCode = TestDiskDrive.STATE_CODE_OPERATION_NOT_PERMITTED
+ return
+ }
+ if (fileOpenMode == 1) { writeMode = true; appendMode = false }
+ else if (fileOpenMode == 2) { writeMode = false; appendMode = true }
+ writeModeLength = inputString.substring(5, inputString.length).toInt()
+ writeBuffer = ByteArray(writeModeLength)
+ writeBufferUsage = 0
+ statusCode = TestDiskDrive.STATE_CODE_STANDBY
+ }
+ else
+ statusCode = TestDiskDrive.STATE_CODE_ILLEGAL_COMMAND
+ }
}
+ private fun sanitisePath(s: String) = s.replace('\\','/').replace(Regex("""\?<>:\*\|"""),"-")
+
+ // applies a "cap" if the path attemps to access parent directory of the root
+ private fun filterSuperRoot(path: String): String {
+ if (path.isEmpty()) return path
+
+ var parentCount = 0
+ val paths = path.split('/')
+ val newPaths = ArrayList()
+ paths.forEach {
+ if (it.isBlank() || it.isEmpty()) {
+ /*do nothing*/
+ }
+ else if (it == "..") {
+ parentCount -= -1
+ }
+ else if (it != ".") {
+ parentCount += 1
+ }
+
+ if (parentCount < -1) parentCount = -1
+
+ if (parentCount >= 0) {
+ newPaths.add(it)
+ }
+ }
+
+ return newPaths.joinToString("/")
+ }
+
+ private fun getReadableLs(): String {
+ val sb = StringBuilder()
+ val isRoot = (file.entryID == 0)
+
+ if (file.isFile) sb.append(file.name)
+ else {
+ sb.append("Current directory: ")
+ sb.append(if (isRoot) "(root)" else file.path)
+ sb.append('\n')
+
+ sb.append(".\n")
+ if (isRoot) sb.append("..\n")
+ // actual entries
+ file.listFiles()!!.forEach {
+ var filenameLen = it.name.length
+
+ sb.append(it.name)
+
+ if (it.isDirectory) {
+ sb.append("/")
+ filenameLen += 1
+ }
+
+ sb.append(" ".repeat(40 - filenameLen))
+
+ if (it.isFile) {
+ sb.append("${it.length()} B")
+ }
+
+ sb.append('\n')
+ }
+ }
+
+ return if (sb.last() == '\n') sb.substring(0, sb.lastIndex) else sb.toString()
+ }
+
+ private fun getSizeStr(): String {
+ val sb = StringBuilder()
+
+ if (file.isFile) sb.append(file.length())
+ else sb.append(file.listFiles()!!.size)
+
+ return sb.toString()
+ }
+
}
\ No newline at end of file
diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/TevdFileDescriptor.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/TevdFileDescriptor.kt
new file mode 100644
index 0000000..c3928b5
--- /dev/null
+++ b/tsvm_core/src/net/torvald/tsvm/peripheral/TevdFileDescriptor.kt
@@ -0,0 +1,125 @@
+package net.torvald.tsvm.peripheral
+
+import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.*
+import net.torvald.tsvm.VM
+import java.io.IOException
+
+/**
+ * Created by minjaesong on 2022-12-17.
+ */
+class TevdFileDescriptor(val DOM: VirtualDisk, _pathstr: String) {
+
+ val path = _pathstr.replace('/', '\\')
+ val vdPath = VDUtil.VDPath(path, VM.CHARSET)
+
+ val entryID: EntryID?
+ get() = VDUtil.getFile(DOM, vdPath)?.entryID
+
+ val canonicalPath: String
+ get() = path
+ val name: String
+ get() = path.substring(path.lastIndexOf('\\') + 1)
+ val nameBytes: ByteArray
+ get() = name.toByteArray(VM.CHARSET)
+
+
+ val isFile: Boolean
+ get() = TODO()
+
+ val isDirectory: Boolean
+ get() = TODO()
+
+
+ private var fileContent: EntryFile? = null
+
+ fun appendBytes(bytes: ByteArray) {
+ fileContent?.getContent()?.appendBytes(bytes)
+ }
+
+ fun writeBytes(bytes: ByteArray) {
+ fileContent?.replaceContent(ByteArray64.fromByteArray(bytes))
+ }
+
+ /**
+ * @param length how many bytes to read
+ * @return actual bytes read, the size may be less than `length` if the actual file size is smaller
+ */
+ fun getHeadBytes64(length: Long): ByteArray64 {
+ if (fileContent == null) throw IOException("No such file or not a file")
+ return fileContent!!.getContent().sliceArray64(0L until length)
+ }
+ /**
+ * @param length how many bytes to read
+ * @return actual bytes read, the size may be less than `length` if the actual file size is smaller
+ */
+ fun getHeadBytes(length: Int): ByteArray {
+ if (fileContent == null) throw IOException("No such file or not a file")
+ return fileContent!!.getContent().sliceArray(0 until length)
+ }
+
+ fun exists(): Boolean {
+ return VDUtil.getFile(DOM, vdPath) != null
+ }
+
+ fun delete() {
+ fileContent = null
+ }
+
+ fun length(): Long {
+ return if (isFile) fileContent?.getSizePure() ?: 0L
+ else -1L
+ }
+
+ fun listFiles(): Array? {
+ if (isFile) return null
+
+ entryID.let {
+ if (it == null) return null
+
+ return VDUtil.getDirectoryEntries(DOM, it).map {
+ TevdFileDescriptor(DOM, path + '\\' + it.getFilenameString(VM.CHARSET))
+ }.toTypedArray()
+ }
+ }
+
+ fun readBytes(): ByteArray {
+ if (isDirectory) throw RuntimeException("Not a file")
+ if (!exists()) throw IOException("File not found")
+ return VDUtil.getAsNormalFile(DOM, vdPath).getContent().toByteArray()
+ }
+
+ fun mkdir(): Boolean {
+ return try {
+ VDUtil.addDir(DOM, vdPath.getParent(), nameBytes)
+ true
+ }
+ catch (e: KotlinNullPointerException) {
+ false
+ }
+ }
+
+ fun createNewFile(): Boolean {
+ fileContent = EntryFile(ByteArray64())
+ val time_t = System.currentTimeMillis() / 1000
+ val newFile = DiskEntry(-1, -1, nameBytes, time_t, time_t, fileContent!!)
+ return try {
+ VDUtil.addFile(DOM, vdPath.getParent(), newFile)
+ true
+ }
+ catch (e: KotlinNullPointerException) {
+ false
+ }
+ }
+
+ fun setLastModified(newTime_t: Long): Boolean {
+ return VDUtil.getFile(DOM, vdPath).let {
+ if (it != null) {
+ it.modificationDate = newTime_t
+ true
+ }
+ else false
+ }
+ }
+
+
+}
\ No newline at end of file