Mercurial > hg > rlgwebd
comparison rlgwebd.js @ 87:bd2cf6dda28d
RLG-Web: use the pty module.
Convert the RLG-Web server to use the pty.js module instead of the
ptyhelper binary.
| author | John "Elwin" Edwards <elwin@sdf.org> |
|---|---|
| date | Mon, 09 Jul 2012 12:24:03 -0700 |
| parents | 4303d94d87a2 |
| children | d644e7d46852 |
comparison
equal
deleted
inserted
replaced
| 86:ad4229cf8321 | 87:bd2cf6dda28d |
|---|---|
| 8 var path = require('path'); | 8 var path = require('path'); |
| 9 var fs = require('fs'); | 9 var fs = require('fs'); |
| 10 var events = require('events'); | 10 var events = require('events'); |
| 11 var child_process = require('child_process'); | 11 var child_process = require('child_process'); |
| 12 var daemon = require(path.join(localModules, "daemon")); | 12 var daemon = require(path.join(localModules, "daemon")); |
| 13 var pty = require(path.join(localModules, "pty.js")); | |
| 13 | 14 |
| 14 /* Configuration variables */ | 15 /* Configuration variables */ |
| 15 // These first two files are NOT in the chroot. | 16 // These first two files are NOT in the chroot. |
| 16 var ctlsocket = "/var/local/rlgwebd/ctl"; | 17 var ctlsocket = "/var/local/rlgwebd/ctl"; |
| 17 var logfile = "/var/local/rlgwebd/log"; | 18 var logfile = "/var/local/rlgwebd/log"; |
| 68 * handlers: (Object) Key-value pairs, event names and functions to | 69 * handlers: (Object) Key-value pairs, event names and functions to |
| 69 * install to handle them. | 70 * install to handle them. |
| 70 * Events: | 71 * Events: |
| 71 * "open": Emitted on startup. Parameters: success (Boolean) | 72 * "open": Emitted on startup. Parameters: success (Boolean) |
| 72 * "data": Data generated by child. Parameters: buf (Buffer) | 73 * "data": Data generated by child. Parameters: buf (Buffer) |
| 73 * "exit": Child terminated. Parameters: exitcode, signal | 74 * "exit": Child terminated. Parameters: none |
| 74 */ | 75 */ |
| 75 function TermSession(game, lkey, dims, handlers) { | 76 function TermSession(game, lkey, dims, handlers) { |
| 76 var ss = this; | 77 var ss = this; |
| 77 /* Subclass EventEmitter to do the hard work. */ | 78 /* Subclass EventEmitter to do the hard work. */ |
| 78 events.EventEmitter.call(this); | 79 events.EventEmitter.call(this); |
| 107 /* Environment. */ | 108 /* Environment. */ |
| 108 var childenv = {}; | 109 var childenv = {}; |
| 109 for (var key in process.env) { | 110 for (var key in process.env) { |
| 110 childenv[key] = process.env[key]; | 111 childenv[key] = process.env[key]; |
| 111 } | 112 } |
| 112 childenv["PTYHELPER"] = String(this.h) + "x" + String(this.w); | 113 var args = ["-n", this.pname]; |
| 113 args = [this.game.path, "-n", this.pname]; | 114 var spawnopts = {"env": childenv, "cwd": "/", "rows": this.h, "cols": this.w, |
| 114 this.child = child_process.spawn("/bin/ptyhelper", args, {"env": childenv}); | 115 "name": "xterm-256color"}; |
| 116 this.term = pty.spawn(this.game.path, args, spawnopts); | |
| 117 tslog("%s playing %s (index %d, pid %d)", this.pname, this.game.uname, | |
| 118 this.sessid, this.term.pid); | |
| 115 this.emit('open', true, this.sessid); | 119 this.emit('open', true, this.sessid); |
| 116 /* Set up the lockfile and ttyrec */ | 120 /* Set up the lockfile and ttyrec */ |
| 117 var ts = timestamp(); | 121 var ts = timestamp(); |
| 118 var progressdir = "/dgldir/inprogress-" + this.game.uname; | 122 var progressdir = "/dgldir/inprogress-" + this.game.uname; |
| 119 this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec"); | 123 this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec"); |
| 120 var lmsg = this.child.pid.toString() + '\n' + this.w + '\n' + this.h + '\n'; | 124 var lmsg = this.term.pid.toString() + '\n' + this.w + '\n' + this.h + '\n'; |
| 121 fs.writeFile(this.lock, lmsg, "utf8"); | 125 fs.writeFile(this.lock, lmsg, "utf8"); |
| 122 var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.game.uname, | 126 var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.game.uname, |
| 123 ts + ".ttyrec"); | 127 ts + ".ttyrec"); |
| 124 this.record = fs.createWriteStream(ttyrec, { mode: 0664 }); | 128 this.record = fs.createWriteStream(ttyrec, { mode: 0664 }); |
| 125 /* Holds the output since the last screen clear, so watchers can begin | 129 /* Holds the output since the last screen clear, so watchers can begin |
| 126 * with a complete screen. */ | 130 * with a complete screen. */ |
| 127 this.framebuf = new Buffer(1024); | 131 this.framebuf = new Buffer(1024); |
| 128 this.frameoff = 0; | 132 this.frameoff = 0; |
| 129 logins[lkey].sessions.push(this.sessid); | 133 logins[lkey].sessions.push(this.sessid); |
| 130 tslog("%s playing %s (index %d, pid %d)", this.pname, this.game.uname, | |
| 131 this.sessid, this.child.pid); | |
| 132 /* END setup */ | 134 /* END setup */ |
| 133 function ttyrec_chunk(buf) { | 135 function ttyrec_chunk(datastr) { |
| 134 var ts = new Date(); | 136 var ts = new Date(); |
| 137 var buf = new Buffer(datastr); | |
| 135 var chunk = new Buffer(buf.length + 12); | 138 var chunk = new Buffer(buf.length + 12); |
| 136 /* TTYREC headers */ | 139 /* TTYREC headers */ |
| 137 chunk.writeUInt32LE(Math.floor(ts.getTime() / 1000), 0); | 140 chunk.writeUInt32LE(Math.floor(ts.getTime() / 1000), 0); |
| 138 chunk.writeUInt32LE(1000 * (ts.getTime() % 1000), 4); | 141 chunk.writeUInt32LE(1000 * (ts.getTime() % 1000), 4); |
| 139 chunk.writeUInt32LE(buf.length, 8); | 142 chunk.writeUInt32LE(buf.length, 8); |
| 140 buf.copy(chunk, 12); | 143 buf.copy(chunk, 12); |
| 141 ss.record.write(chunk); | 144 ss.record.write(chunk); |
| 142 ss.framepush(buf); | 145 ss.framepush(buf); |
| 143 ss.emit('data', buf); | 146 ss.emit('data', buf); |
| 144 } | 147 } |
| 145 this.child.stdout.on("data", ttyrec_chunk); | 148 this.term.on("data", ttyrec_chunk); |
| 146 this.child.stderr.on("data", ttyrec_chunk); | |
| 147 this.framepush = function(chunk) { | 149 this.framepush = function(chunk) { |
| 148 /* If this chunk resets the screen, discard what preceded it. */ | 150 /* If this chunk resets the screen, discard what preceded it. */ |
| 149 if (bufncmp(chunk, this.game.clear, this.game.clear.length)) { | 151 if (bufncmp(chunk, this.game.clear, this.game.clear.length)) { |
| 150 this.framebuf = new Buffer(1024); | 152 this.framebuf = new Buffer(1024); |
| 151 this.frameoff = 0; | 153 this.frameoff = 0; |
| 162 } | 164 } |
| 163 chunk.copy(this.framebuf, this.frameoff); | 165 chunk.copy(this.framebuf, this.frameoff); |
| 164 this.frameoff += chunk.length; | 166 this.frameoff += chunk.length; |
| 165 }; | 167 }; |
| 166 this.write = function(data) { | 168 this.write = function(data) { |
| 167 this.child.stdin.write(data); | 169 this.term.write(data); |
| 168 }; | 170 }; |
| 169 this.child.on("exit", function (code, signal) { | 171 this.term.on("exit", function () { |
| 170 fs.unlink(ss.lock); | 172 fs.unlink(ss.lock); |
| 171 ss.record.end(); | 173 ss.record.end(); |
| 172 ss.emit('exit', code, signal); | 174 ss.emit('exit'); |
| 173 var id = ss.sessid; | 175 var id = ss.sessid; |
| 174 delete sessions[id]; | 176 delete sessions[id]; |
| 175 tslog("Game %s ended.", id); | 177 tslog("Game %s ended.", id); |
| 176 }); | 178 }); |
| 177 this.close = function () { | 179 this.close = function () { |
| 178 this.child.kill('SIGHUP'); | 180 this.term.kill('SIGHUP'); |
| 179 }; | 181 }; |
| 180 } | 182 } |
| 181 TermSession.prototype = new events.EventEmitter(); | 183 TermSession.prototype = new events.EventEmitter(); |
| 182 | 184 |
| 183 function Watcher(session) { | 185 function Watcher(session) { |
| 201 reply.t = "d"; | 203 reply.t = "d"; |
| 202 reply.n = ss.nsend++; | 204 reply.n = ss.nsend++; |
| 203 reply.d = buf.toString("hex"); | 205 reply.d = buf.toString("hex"); |
| 204 ss.sendQ.push(reply); | 206 ss.sendQ.push(reply); |
| 205 } | 207 } |
| 206 function exitH(code, signal) { | 208 function exitH() { |
| 207 ss.alive = false; | 209 ss.alive = false; |
| 208 ss.sendQ.push({"t": "q"}); | 210 ss.sendQ.push({"t": "q"}); |
| 209 } | 211 } |
| 210 session.on('data', dataH); | 212 session.on('data', dataH); |
| 211 session.on('exit', exitH); | 213 session.on('exit', exitH); |
| 325 reply.t = "d"; | 327 reply.t = "d"; |
| 326 reply.n = ss.nsend++; | 328 reply.n = ss.nsend++; |
| 327 reply.d = chunk.toString("hex"); | 329 reply.d = chunk.toString("hex"); |
| 328 ss.sendQ.push(reply); | 330 ss.sendQ.push(reply); |
| 329 } | 331 } |
| 330 function exitH(code, signal) { | 332 function exitH() { |
| 331 ss.alive = false; | 333 ss.alive = false; |
| 332 ss.sendQ.push({"t": "q"}); | 334 ss.sendQ.push({"t": "q"}); |
| 333 } | 335 } |
| 334 var handlers = {'open': openH, 'data': dataH, 'exit': exitH}; | 336 var handlers = {'open': openH, 'data': dataH, 'exit': exitH}; |
| 335 this.session = new TermSession(gamename, lkey, dims, handlers); | 337 this.session = new TermSession(gamename, lkey, dims, handlers); |
| 1008 } | 1010 } |
| 1009 } | 1011 } |
| 1010 | 1012 |
| 1011 process.on("exit", function () { | 1013 process.on("exit", function () { |
| 1012 for (var sessid in sessions) { | 1014 for (var sessid in sessions) { |
| 1013 sessions[sessid].child.kill('SIGHUP'); | 1015 sessions[sessid].term.kill('SIGHUP'); |
| 1014 } | 1016 } |
| 1015 tslog("Quitting..."); | 1017 tslog("Quitting..."); |
| 1016 return; | 1018 return; |
| 1017 }); | 1019 }); |
| 1018 | 1020 |
