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 |