diff --git a/assets/disk0/tvdos/TVDOS.SYS b/assets/disk0/tvdos/TVDOS.SYS index 4e4a73b..55ebeb7 100644 --- a/assets/disk0/tvdos/TVDOS.SYS +++ b/assets/disk0/tvdos/TVDOS.SYS @@ -172,6 +172,12 @@ filesystem.mkFile = (driveLetter) => { var response = com.getStatusCode(port[0]); return (response === 0); }; +filesystem.delete = (driveLetter) => { + var port = filesystem._toPorts(driveLetter); + com.sendMessage(port[0], "DELETE"); + var response = com.getStatusCode(port[0]); + return (response === 0); +}; Object.freeze(filesystem); /////////////////////////////////////////////////////////////////////////////// diff --git a/assets/disk0/tvdos/bin/command.js b/assets/disk0/tvdos/bin/command.js index 95d77b0..359cffc 100644 --- a/assets/disk0/tvdos/bin/command.js +++ b/assets/disk0/tvdos/bin/command.js @@ -275,6 +275,16 @@ shell.coreutils = { * but do instead: * if (args[1] === undefined) */ + + cat: function(args) { + var pathstr = (args[1] !== undefined) ? args[1] : shell.getPwdString(); + + var pathOpenedStatus = filesystem.open(CURRENT_DRIVE, pathstr, 'R'); + if (pathOpenedStatus != 0) { printerrln("File not found"); return pathOpenedStatus; } + let contents = filesystem.readAll(CURRENT_DRIVE); + // TODO just print out what's there + print(contents); + }, cd: function(args) { if (args[1] === undefined) { println(CURRENT_DRIVE+":"+shell_pwd.join("/")); @@ -286,10 +296,94 @@ shell.coreutils = { // check if path is valid var dirOpenedStatus = filesystem.open(CURRENT_DRIVE, path.string, 'R'); var isDir = filesystem.isDirectory(CURRENT_DRIVE); // open a dir; if path is nonexistent, file won't actually be opened - if (!isDir) { printerrln("CHDIR failed for '"+path.string+"'"); return dirOpenedStatus; } // if file is not opened, IO error code will be returned + if (!isDir) { printerrln(`${args[0].toUpperCase()} failed for '${path.string}'`); return dirOpenedStatus; } // if file is not opened, IO error code will be returned shell_pwd = path.pwd; }, + cls: function(args) { + con.clear(); + graphics.clearPixels(255); + graphics.clearPixels2(240); + }, + cp: function(args) { + if (args[2] === undefined) { + printerrln("Syntax error") + return + } + else if (args[1] === undefined) { + printerrln(`Usage: ${args[0].toUpperCase()} SOURCE DEST`) + return + } + let path = shell.resolvePathInput(args[1]) + let pathd = shell.resolvePathInput(args[2]) + + let dirOpenedStatus = filesystem.open(CURRENT_DRIVE, path.string, 'R') + let isDir = filesystem.isDirectory(CURRENT_DRIVE) + if (isDir || dirOpenedStatus != 0) { printerrln(`${args[0].toUpperCase()} failed for '${path.string}'`); return dirOpenedStatus; } // if file is directory or failed to open, IO error code will be returned + + + let bytes = filesystem.readAllBytes(CURRENT_DRIVE) + + + dirOpenedStatus = filesystem.open(CURRENT_DRIVE, pathd.string, 'W') + isDir = filesystem.isDirectory(CURRENT_DRIVE) + if (isDir || dirOpenedStatus != 0) { printerrln(`${args[0].toUpperCase()} failed for '${pathd.string}'`); return dirOpenedStatus; } // if file is directory or failed to open, IO error code will be returned + + + filesystem.writeBytes(CURRENT_DRIVE, bytes) + }, + date: function(args) { + let monthNames = ["Spring", "Summer", "Autumn", "Winter"] + let dayNames = ["Mondag", "Tysdag", "Midtveke", "Torsdag", "Fredag", "Laurdag", "Sundag", "Verddag"] + + let msec = sys.currentTimeInMills() + while (msec == 0) { + msec = sys.currentTimeInMills() + } + + let secs = ((msec / 1000)|0) % 60 + let timeInMinutes = ((msec / 60000)|0) + let mins = timeInMinutes % 60 + let hours = ((timeInMinutes / 60)|0) % 24 + let ordinalDay = ((timeInMinutes / (60*24))|0) % 120 + let visualDay = (ordinalDay % 30) + 1 + let months = ((timeInMinutes / (60*24*30))|0) % 4 + let dayName = ordinalDay % 7 // 0 for Mondag + if (ordinalDay == 119) dayName = 7 // Verddag + let years = ((timeInMinutes / (60*24*30*120))|0) + 125 + + println(`\xE7${years} ${monthNames[months]} ${visualDay} ${dayNames[dayName]}, ${(''+hours).padStart(2,'0')}:${(''+mins).padStart(2,'0')}:${(''+secs).padStart(2,'0')}`) + }, + dir: function(args) { + var pathstr = (args[1] !== undefined) ? args[1] : shell.getPwdString(); + + // check if path is valid + var pathOpenedStatus = filesystem.open(CURRENT_DRIVE, pathstr, 'R'); + if (pathOpenedStatus != 0) { printerrln("File not found"); return pathOpenedStatus; } + + var port = filesystem._toPorts(CURRENT_DRIVE)[0] + com.sendMessage(port, "LIST"); + println(com.pullMessage(port)); + }, + del: function(args) { + if (args[1] === undefined) { + printerrln("Syntax error"); + return + } + + var pathOpenedStatus = filesystem.open(CURRENT_DRIVE, args[1], 'R'); + if (pathOpenedStatus != 0) { printerrln("File not found"); return pathOpenedStatus; } + return filesystem.delete(CURRENT_DRIVE) + }, + echo: function(args) { + if (args[1] !== undefined) { + args.forEach(function(it,i) { if (i > 0) print(shell.replaceVarCall(it)+" ") }); + } + println(); + }, + exit: function(args) { + cmdExit = true; + }, mkdir: function(args) { if (args[1] === undefined) { printerrln("Syntax error"); @@ -301,24 +395,7 @@ shell.coreutils = { // check if path is valid var dirOpenedStatus = filesystem.open(CURRENT_DRIVE, path.string, 'W'); var mkdird = filesystem.mkDir(CURRENT_DRIVE); - if (!mkdird) { printerrln("MKDIR failed for '"+path.string+"'"); return dirOpenedStatus; } - }, - cls: function(args) { - con.clear(); - graphics.clearPixels(255); - graphics.clearPixels2(240); - }, - exit: function(args) { - cmdExit = true; - }, - ver: function(args) { - println(welcome_text); - }, - echo: function(args) { - if (args[1] !== undefined) { - args.forEach(function(it,i) { if (i > 0) print(shell.replaceVarCall(it)+" ") }); - } - println(); + if (!mkdird) { printerrln(`${args[0].toUpperCase()} failed for '${path.string}'`); return dirOpenedStatus; } }, rem: function(args) { return 0; @@ -358,31 +435,21 @@ shell.coreutils = { } } }, - dir: function(args) { - var pathstr = (args[1] !== undefined) ? args[1] : shell.getPwdString(); - - // check if path is valid - var pathOpenedStatus = filesystem.open(CURRENT_DRIVE, pathstr, 'R'); - if (pathOpenedStatus != 0) { printerrln("File not found"); return pathOpenedStatus; } - - var port = filesystem._toPorts(CURRENT_DRIVE)[0] - com.sendMessage(port, "LIST"); - println(com.pullMessage(port)); - }, - cat: function(args) { - var pathstr = (args[1] !== undefined) ? args[1] : shell.getPwdString(); - - var pathOpenedStatus = filesystem.open(CURRENT_DRIVE, pathstr, 'R'); - if (pathOpenedStatus != 0) { printerrln("File not found"); return pathOpenedStatus; } - let contents = filesystem.readAll(CURRENT_DRIVE); - // TODO just print out what's there - print(contents); + ver: function(args) { + println(welcome_text); }, panic: function(args) { throw Error("Panicking command.js") } }; -shell.coreutils.chdir = shell.coreutils.cd; +// define command aliases here +shell.coreutils.chdir = shell.coreutils.cd +shell.coreutils.copy = shell.coreutils.cp +shell.coreutils.erase = shell.coreutils.del +shell.coreutils.rm = shell.coreutils.del +shell.coreutils.ls = shell.coreutils.dir +shell.coreutils.time = shell.coreutils.date +// end of command aliases Object.freeze(shell.coreutils); shell.stdio = { out: { diff --git a/assets/disk0/tvdos/bin/fsh.js b/assets/disk0/tvdos/bin/fsh.js index 6fd9eae..c3e4d5c 100644 --- a/assets/disk0/tvdos/bin/fsh.js +++ b/assets/disk0/tvdos/bin/fsh.js @@ -102,9 +102,8 @@ clockWidget.draw = function(charXoff, charYoff) { con.move(1 + charYoff, 17 + charXoff); print(clockWidget.monthNames[months]+" "+visualDay); // print year and dayname - con.mvaddch(2 + charYoff, 17 + charXoff, 5); - con.move(2 + charYoff, 18 + charXoff); - print(years+" "+clockWidget.dayNames[dayName]); + con.move(2 + charYoff, 17 + charXoff); + print("\xE7"+years+" "+clockWidget.dayNames[dayName]); }; diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/SerialStdioHost.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/SerialStdioHost.kt index 9768ab4..071416e 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/SerialStdioHost.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/SerialStdioHost.kt @@ -7,23 +7,43 @@ import java.io.OutputStream /** * Created by minjaesong on 2022-05-23. */ -class SerialStdioHost(val hostVM: VM) : BlockTransferInterface(true, true) { +class SerialStdioHost(val runnerVM: VM) : BlockTransferInterface(true, true) { + + /** + * - IDLE: Initial status + * - HOST: Usually the server computer + * - TERMINAL: Usually the user computer that connects to the host + * - PRINTSTREAM: When set, any received bytes will go into the monitor + * - INPUTSTREAM: When set, any received bytes will be handled internally (usually, when host sets the terminal to this mode, the host will interpret any data sent from the terminal as input by the human behind the terminal) + */ + enum class Mode { + IDLE, HOST, CLIENT, PRINTSTREAM, INPUTSTREAM + } var otherVM: VM? = null + private var mode: Mode = Mode.IDLE + private var stream: Mode = Mode.IDLE + override fun attachDevice(device: BlockTransferInterface?) { if (device !is SerialStdioHost) throw IllegalArgumentException("Other device is not SerialStdioHost: ${device?.javaClass?.canonicalName}") super.attachDevice(device) - otherVM = device.hostVM + otherVM = device.runnerVM } - private fun getOthersFirstGPU(): GraphicsAdapter? { - return otherVM!!.findPeribyType(VM.PERITYPE_GPU_AND_TERM)?.peripheral as? GraphicsAdapter + private fun getOthersFirstGPU(): GraphicsAdapter { + return otherVM!!.findPeribyType(VM.PERITYPE_GPU_AND_TERM)?.peripheral as GraphicsAdapter } + private fun getHostsFirstGPU(): GraphicsAdapter { + return runnerVM.findPeribyType(VM.PERITYPE_GPU_AND_TERM)?.peripheral as GraphicsAdapter + } + + // sends a byte to client's GPU val out = object : OutputStream() { override fun write(p0: Int) { - getOthersFirstGPU()?.writeOut(p0.toByte()) + if ((recipient as SerialStdioHost).readyToBePossessed) + getOthersFirstGPU().writeOut(p0.toByte()) } } @@ -32,29 +52,34 @@ class SerialStdioHost(val hostVM: VM) : BlockTransferInterface(true, true) { private val SGI_RESET = byteArrayOf(0x1B, 0x5B, 0x6D) override fun write(p0: Int) { - getOthersFirstGPU()?.let { g -> - SGI_RED.forEach { g.writeOut(it) } - g.writeOut(p0.toByte()) - SGI_RESET.forEach { g.writeOut(it) } + if ((recipient as SerialStdioHost).readyToBePossessed) { + getOthersFirstGPU().let { g -> + SGI_RED.forEach { g.writeOut(it) } + g.writeOut(p0.toByte()) + SGI_RESET.forEach { g.writeOut(it) } + } } } override fun write(p0: ByteArray) { - getOthersFirstGPU()?.let { g -> - SGI_RED.forEach { g.writeOut(it) } - p0.forEach { g.writeOut(it) } - SGI_RESET.forEach { g.writeOut(it) } + if ((recipient as SerialStdioHost).readyToBePossessed) { + getOthersFirstGPU().let { g -> + SGI_RED.forEach { g.writeOut(it) } + p0.forEach { g.writeOut(it) } + SGI_RESET.forEach { g.writeOut(it) } + } } } } + // sends a byte from the client to the host val `in` = object : InputStream() { init { otherVM?.getIO()?.mmio_write(38L, 1) } override fun read(): Int { - if (otherVM != null) { + if (otherVM != null && (recipient as SerialStdioHost).readyToBePossessed) { var key: Byte do { Thread.sleep(4L) // if spinning rate is too fast, this function will fail. @@ -77,13 +102,84 @@ class SerialStdioHost(val hostVM: VM) : BlockTransferInterface(true, true) { TODO("Not yet implemented") } + private var readyToBePossessed = false + + /** + * Commands: + * + * - "LISTEN": when idle, sets the device to TERMINAL mode + * - "HOST": when idle, sets the device to HOST mode + * - \x14: hangs up the connection and routes stdio back to the TERMINAL's GraphicsAdapter + * + * // NOTE TO SELF: are these necessary? + * + * - \x11: tells TERMINAL to enter printstream mode (routes stdio to this device) + * - \x12: tells TERMINAL to enter datastream mode + * - \x13: tells TERMINAL to enter keyboard-read mode + */ override fun writeoutImpl(inputData: ByteArray) { - TODO("Not yet implemented") + val inputString = inputData.trimNull().toString(VM.CHARSET) + + if (mode == Mode.IDLE) { + if (inputString.startsWith("LISTEN")) { + mode = Mode.CLIENT + readyToBePossessed = true + } + else if (inputString.startsWith("HOST")) { + mode = Mode.HOST + hijackHostPrint() + hijackClientRead() + } + } + else { + if (mode == Mode.HOST) { + when (inputData[0]) { + DC_HUP -> { + releaseHostPrint() + releaseClientRead() + mode = Mode.IDLE + } + } + } + else if (mode == Mode.CLIENT) { + when (inputData[0]) { + DC_HUP -> { + mode = Mode.IDLE + readyToBePossessed = false + } + } + } + } + } + + private fun hijackHostPrint() { + runnerVM.getPrintStream = { this.out } + runnerVM.getErrorStream = { this.err } + } + + private fun hijackClientRead() { + otherVM!!.getInputStream = { this.`in` } + } + + private fun releaseHostPrint() { + getHostsFirstGPU().let { + runnerVM.getPrintStream = { it.getPrintStream() } + runnerVM.getErrorStream = { it.getErrorStream() } + } + } + + private fun releaseClientRead() { + otherVM!!.getInputStream = { getOthersFirstGPU().getInputStream() } } override fun hasNext(): Boolean { TODO("Not yet implemented") } - + private companion object { + const val DC_PRINT = 0x11.toByte() + const val DC_DATA = 0x12.toByte() + const val DC_INPUT = 0x13.toByte() + const val DC_HUP = 0x14.toByte() + } } \ No newline at end of file diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt index 6299b72..6262ad7 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt @@ -215,6 +215,23 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath: } blockSendCount = 0 } + else if (inputString.startsWith("DELETE")) { + if (!fileOpen) { + statusCode = STATE_CODE_NO_FILE_OPENED + return + } + try { + file.delete() + } + catch (e: SecurityException) { + statusCode = STATE_CODE_SYSTEM_SECURITY_ERROR + return + } + catch (e1: IOException) { + statusCode = STATE_CODE_SYSTEM_IO_ERROR + return + } + } else if (inputString.startsWith("LISTFILES")) { // TODO temporary behaviour to ignore any arguments resetBuf()