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