michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: */ michael@0: michael@0: /* michael@0: * ChromeWorker Object subprocess.jsm on Unix-like systems (Linux, Mac OS X, ...) michael@0: * to process stdin/stdout/stderr on separate threads. michael@0: * michael@0: */ michael@0: michael@0: // Being a ChromeWorker object, implicitly uses the following: michael@0: // Components.utils.import("resource://gre/modules/ctypes.jsm"); michael@0: michael@0: 'use strict'; michael@0: michael@0: const BufferSize = 1024; michael@0: michael@0: var libc = null; michael@0: var libcFunc = {}; michael@0: michael@0: michael@0: /* michael@0: struct pollfd { michael@0: int fd; // file descriptor michael@0: short events; // events to look for michael@0: short revents; // events returned michael@0: }; michael@0: */ michael@0: michael@0: var pollfd = new ctypes.StructType("pollfd", michael@0: [ {'fd': ctypes.int}, michael@0: {'events': ctypes.short}, michael@0: {'revents': ctypes.short} michael@0: ]); michael@0: michael@0: var WriteBuffer = ctypes.uint8_t.array(BufferSize); michael@0: var ReadBuffer = ctypes.char.array(BufferSize); michael@0: michael@0: michael@0: const POLLIN = 0x0001; michael@0: const POLLOUT = 0x0004; michael@0: michael@0: const POLLERR = 0x0008; // some poll error occurred michael@0: const POLLHUP = 0x0010; // file descriptor was "hung up" michael@0: const POLLNVAL = 0x0020; // requested events "invalid" michael@0: michael@0: const WNOHANG = 0x01; michael@0: michael@0: const pid_t = ctypes.int32_t; michael@0: michael@0: const INDEFINITE = -1; michael@0: const NOWAIT = 0; michael@0: const WAITTIME = 200 // wait time for poll() in ms michael@0: michael@0: function initLibc(libName) { michael@0: postMessage({msg: "debug", data: "initialising library with "+ libName}); michael@0: michael@0: libc = ctypes.open(libName); michael@0: michael@0: libcFunc.pollFds = pollfd.array(1); michael@0: michael@0: // int poll(struct pollfd fds[], nfds_t nfds, int timeout); michael@0: libcFunc.poll = libc.declare("poll", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: libcFunc.pollFds, michael@0: ctypes.unsigned_int, michael@0: ctypes.int); michael@0: michael@0: //ssize_t write(int fd, const void *buf, size_t count); michael@0: // NOTE: buf is declared as array of unsigned int8 instead of char to avoid michael@0: // implicit charset conversion michael@0: libcFunc.write = libc.declare("write", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: ctypes.int, michael@0: WriteBuffer, michael@0: ctypes.int); michael@0: michael@0: //int read(int fd, void *buf, size_t count); michael@0: libcFunc.read = libc.declare("read", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: ctypes.int, michael@0: ReadBuffer, michael@0: ctypes.int); michael@0: michael@0: //int pipe(int pipefd[2]); michael@0: libcFunc.pipefd = ctypes.int.array(2); michael@0: michael@0: //int close(int fd); michael@0: libcFunc.close = libc.declare("close", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: ctypes.int); michael@0: michael@0: //pid_t waitpid(pid_t pid, int *status, int options); michael@0: libcFunc.waitpid = libc.declare("waitpid", michael@0: ctypes.default_abi, michael@0: pid_t, michael@0: pid_t, michael@0: ctypes.int.ptr, michael@0: ctypes.int); michael@0: } michael@0: michael@0: function closePipe(pipe) { michael@0: libcFunc.close(pipe); michael@0: } michael@0: michael@0: function writePipe(pipe, data) { michael@0: michael@0: postMessage({msg: "debug", data: "trying to write to "+pipe}); michael@0: michael@0: let numChunks = Math.floor(data.length / BufferSize); michael@0: let pData = new WriteBuffer(); michael@0: michael@0: for (var chunk = 0; chunk <= numChunks; chunk ++) { michael@0: let numBytes = chunk < numChunks ? BufferSize : data.length - chunk * BufferSize; michael@0: for (var i=0; i < numBytes; i++) { michael@0: pData[i] = data.charCodeAt(chunk * BufferSize + i) % 256; michael@0: } michael@0: michael@0: let bytesWritten = libcFunc.write(pipe, pData, numBytes); michael@0: if (bytesWritten != numBytes) { michael@0: closePipe(); michael@0: libc.close(); michael@0: postMessage({ msg: "error", data: "error: wrote "+bytesWritten+" instead of "+numBytes+" bytes"}); michael@0: close(); michael@0: } michael@0: } michael@0: postMessage({msg: "info", data: "wrote "+data.length+" bytes of data"}); michael@0: } michael@0: michael@0: michael@0: function readString(data, length, charset) { michael@0: var string = '', bytes = []; michael@0: for(var i = 0;i < length; i++) { michael@0: if(data[i] == 0 && charset != "null") // stop on NULL character for non-binary data michael@0: break; michael@0: michael@0: bytes.push(data[i]); michael@0: } michael@0: michael@0: return bytes; michael@0: } michael@0: michael@0: function readPipe(pipe, charset, pid) { michael@0: var p = new libcFunc.pollFds; michael@0: p[0].fd = pipe; michael@0: p[0].events = POLLIN | POLLERR | POLLHUP; michael@0: p[0].revents = 0; michael@0: var pollTimeout = WAITTIME; michael@0: var exitCode = -1; michael@0: var readCount = 0; michael@0: var result, status = ctypes.int(); michael@0: result = 0; michael@0: michael@0: michael@0: const i=0; michael@0: while (true) { michael@0: if (result == 0) { michael@0: result = libcFunc.waitpid(pid, status.address(), WNOHANG); michael@0: if (result > 0) { michael@0: pollTimeout = NOWAIT; michael@0: exitCode = parseInt(status.value); michael@0: postMessage({msg: "debug", data: "waitpid signaled subprocess stop, exitcode="+status.value }); michael@0: } michael@0: } michael@0: var r = libcFunc.poll(p, 1, pollTimeout); michael@0: if (r > 0) { michael@0: if (p[i].revents & POLLIN) { michael@0: postMessage({msg: "debug", data: "reading next chunk"}); michael@0: readCount = readPolledFd(p[i].fd, charset); michael@0: if (readCount == 0) break; michael@0: } michael@0: michael@0: if (p[i].revents & POLLHUP) { michael@0: postMessage({msg: "debug", data: "poll returned HUP"}); michael@0: break; michael@0: } michael@0: else if (p[i].revents & POLLERR) { michael@0: postMessage({msg: "error", data: "poll returned error"}); michael@0: break; michael@0: } michael@0: else if (p[i].revents != POLLIN) { michael@0: postMessage({msg: "error", data: "poll returned "+p[i]}); michael@0: break; michael@0: } michael@0: } michael@0: else michael@0: if (pollTimeout == 0 || r < 0) break; michael@0: } michael@0: michael@0: // continue reading until the buffer is empty michael@0: while (readCount > 0) { michael@0: readCount = readPolledFd(pipe, charset); michael@0: } michael@0: michael@0: libcFunc.close(pipe); michael@0: postMessage({msg: "done", data: exitCode }); michael@0: libc.close(); michael@0: close(); michael@0: } michael@0: michael@0: function readPolledFd(pipe, charset) { michael@0: var line = new ReadBuffer(); michael@0: var r = libcFunc.read(pipe, line, BufferSize); michael@0: michael@0: if (r > 0) { michael@0: var c = readString(line, r, charset); michael@0: postMessage({msg: "data", data: c, count: c.length}); michael@0: } michael@0: return r; michael@0: } michael@0: michael@0: onmessage = function (event) { michael@0: switch (event.data.msg) { michael@0: case "init": michael@0: initLibc(event.data.libc); michael@0: break; michael@0: case "read": michael@0: initLibc(event.data.libc); michael@0: readPipe(event.data.pipe, event.data.charset, event.data.pid); michael@0: break; michael@0: case "write": michael@0: // data contents: michael@0: // msg: 'write' michael@0: // data: the data (string) to write michael@0: // pipe: ptr to pipe michael@0: writePipe(event.data.pipe, event.data.data); michael@0: postMessage({msg: "info", data: "WriteOK"}); michael@0: break; michael@0: case "close": michael@0: postMessage({msg: "debug", data: "closing stdin\n"}); michael@0: michael@0: closePipe(event.data.pipe); michael@0: postMessage({msg: "info", data: "ClosedOK"}); michael@0: break; michael@0: case "stop": michael@0: libc.close(); // do not use libc after this point michael@0: close(); michael@0: break; michael@0: default: michael@0: throw("error: Unknown command"+event.data.msg+"\n"); michael@0: } michael@0: return; michael@0: };