@luminati-io/luminati-proxy
Version:
A configurable local proxy for luminati.io
322 lines (316 loc) • 10.1 kB
JavaScript
// LICENSE_CODE ZON
; /*jslint node:true*/
require('./config.js');
const etask = require('./etask.js');
const file = require('./file.js');
const string = require('./string.js');
const array = require('./array.js');
const child_process = require('child_process');
const fs = require('fs');
const E = exports;
const assign = Object.assign;
E.process_opt = opt=>{
opt = assign({}, opt);
if (opt.out=='stdall')
opt.stdall = true;
if (opt.stdall)
opt.stdout = opt.stderr = true;
if (!opt.out && (opt.stdout || opt.stderr))
opt.out = 'obj';
if (!opt.out)
opt.out = 'unix';
if (opt.out=='stdout')
opt.stdout = true;
if (opt.out=='stderr')
opt.stderr = true;
// XXX sergey: must use pipes only on required ios
if (opt.stdin || opt.stdout || opt.stderr)
opt.piped = true;
if (opt.cmd_shell)
opt.opt = assign({shell: true}, opt.opt);
return opt;
};
E.get_cmd = (cmd, opt)=>{
let args = array.copy(array.to_array(cmd));
// XXX vladimir: rm process.zon from hutil
if (opt.node && process.zon && process.zon.main)
throw 'Unable to spawn node process from embedded Node';
let command = opt.cmd_shell ? `"${args.shift()}"` :
opt.shell ? '/bin/bash' : opt.node ? process.argv[0] : args.shift();
command = file.normalize(command);
if (opt.cmd_shell)
args = args.map(a=>`"${a}"`);
else if (opt.shell)
args = ['-c', args.join(' ')];
else if (opt.node&&args.length)
args[0] = file.normalize(args[0]);
if (opt.node&&opt.node_args)
args.unshift(...opt.node_args);
return {cmd: command, argv: args};
};
E.get_env = opt=>{
if (!opt.opt||!opt.opt.env)
return process.env;
if (!opt.node)
return opt.opt.env;
let nave_env = {};
string.qw`NAVEPATH NAVEBIN NAVELIB NAVEVERSION NAVENAME NAVE_DIR NAVE
npm_config_binroot npm_config_root npm_config_manroot
npm_config_prefix NODE_MODULES NODE_PATH`.forEach(v=>{
nave_env[v] = process.env[v]; });
return assign(nave_env, opt.opt.env);
};
E.get_redir = (input, piped, ipc)=>{
let redir_modes = {'<': 'r', '>': 'w', '>>': 'a'};
let outputs = [0, 1, 2], fds = [];
input = input||'';
if (piped)
input = '0</dev/pipe 1>/dev/pipe 2>/dev/pipe '+input;
let redirs = string.qw(input), match;
for (let r=0; r<redirs.length; r++)
{
if (!(match = /^(\d*)(<|>|>>)(&(\d+)|[^>&].*)$/.exec(redirs[r])))
throw new Error('Invalid redirection '+redirs[r]);
let src, dst;
src = match[1] ? +match[1] : match[2]=='<' ? 0 : 1;
if (match[4]) // to fd
{
dst = +match[4];
if (dst in outputs)
dst = outputs[dst];
}
else // to file
{
dst = match[3];
if (/^\/dev\/pipe$/.test(dst))
dst += src; // remember src fd
if (!/^\/dev\/(null|pipe)/.test(dst)) // real file
{
fds.push(dst = fs.openSync(dst, redir_modes[match[2]]));
}
}
outputs[src] = dst;
}
let pipes = {};
for (let i=0; i<outputs.length; i++)
{
let o = outputs[i], m;
if (o=='/dev/null')
o = 'ignore';
else if (m = /^\/dev\/pipe(\d)$/.exec(o))
{
o = 'pipe';
if (+m[1]!=i)
pipes[i] = +m[1];
}
outputs[i] = o;
}
if (ipc)
outputs.push('ipc');
return {stdio: outputs, pipes: pipes, fds: fds};
};
E.get_redir_sync = (redir, stdio)=>{
if (redir)
return E.get_redir(redir);
return {stdio: stdio||'pipe', fds: []};
};
function unshift_pipe(stdio, data){
// XXX sergey: find out what causes stream to close
if (stdio && !stdio._readableState.ended)
stdio.unshift(data);
}
function handle_stdio(child, redir, logger){
let io_names = ['stdin', 'stdout', 'stderr'];
child.stdio.forEach((stdio, i)=>{
if (i<1||!stdio)
return;
let method = !(i in redir) ? data=>logger(io_names[i], data) :
data=>unshift_pipe(child.stdio[redir[i]], data);
stdio.on('data', method);
});
}
function close_fds(child, fds){
fds.forEach(fs.closeSync);
if (!child)
return;
child.stdio.forEach(stdio=>{
if (stdio)
stdio.destroy();
});
}
// XXX vladimir: check about platforms other than x86/arm from signal(7)
const sigstring_int = {
// posix.1-1990
SIGHUP: 1,
SIGINT: 2,
SIGQUIT: 3,
SIGILL: 4,
SIGABRT: 6,
SIGFPE: 8,
SIGKILL: 9,
SIGSEGV: 11,
SIGPIPE: 13,
SIGALRM: 14,
SIGTERM: 15,
SIGUSR1: 10,
SIGUSR2: 12,
SIGCHLD: 17,
SIGCONT: 18,
SIGSTOP: 19,
SIGTSTP: 20,
SIGTTIN: 21,
SIGTTOU: 22,
// posix.1-2001
SIGBUS: 7,
SIGPOLL: 29,
SIGPROF: 27,
SIGSYS: 31,
SIGTRAP: 5,
SIGURG: 23,
SIGVTALRM: 26,
SIGXCPU: 24,
SIGXFSZ: 25,
// various
SIGIOT: 6,
SIGSTKFLT: 16,
SIGIO: 29,
SIGPWR: 30,
SIGINFO: 30,
SIGWINCH: 28,
SIGUNUSED: 31,
};
E.sys = (cmd, opt)=>etask(function*exec(){
opt = E.process_opt(opt||{});
// XXX vladimir: rm process.zon from hutil
if (opt.fork && process.zon && process.zon.main)
throw 'Unable to spawn node process from embedded Node';
cmd = E.get_cmd(cmd, opt);
let status = {signal: 0, code: 0};
let log = {stdout: '', stderr: '', stdall: ''};
let kill_signal = opt.kill_signal||'SIGINT';
let child;
let redir_str = opt.redir||'';
this.info.cmd = ()=>cmd.cmd+' '+cmd.argv.join(' ')+redir_str;
if (opt.verbose)
console.log(cmd.cmd, cmd.argv.join(' '), redir_str);
let redir = E.get_redir(redir_str, opt.piped, opt.fork);
let opts = assign({stdio: redir.stdio, cwd: opt.cwd}, opt.opt);
this.finally(()=>{
close_fds(child, redir.fds);
if (child && !child.killed && child.exitCode===null)
child.kill(kill_signal);
});
let error;
let on_error = err=>{
error = err;
this.continue();
};
// To kill children of process it needed to be run in detached state
if (opt.timeout||opt.detached)
opts.detached = true;
opts.env = E.get_env(opt);
if (opt.fork)
child = child_process.fork(cmd.cmd, cmd.argv, opts);
else
child = child_process.spawn(cmd.cmd, cmd.argv, opts);
child.on('error', on_error);
child.on('close', (code, signal)=>{
status.code = code;
status.signal = signal;
this.continue();
});
if (opt.on_msg)
child.on('message', msg=>opt.on_msg(child, msg));
if (opt.on_disc)
child.on('disconnect', msg=>opt.on_disc(child));
if (opt.timeout||opt.detached)
this.child_process = child;
// Process stdio redirection
let logger = (name, data)=>{
if (opt[name])
log[name] += data;
if (opt.stdall)
log.stdall += data;
if (opt.log)
opt.log(data, name);
};
handle_stdio(child, redir.pipes, logger);
if (opt.stdin&&child.stdio[0])
{
child.stdio[0].end(opt.stdin);
child.stdio[0].once('error', on_error);
}
if (opt.timeout)
this.alarm(opt.timeout, on_error.bind(null, 'timeout'));
yield this.wait();
if (error==='timeout' && !child.killed)
process.kill(-child.pid);
let rv = error ? -1 : status.code;
let ret = {stdout: log.stdout, stderr: log.stderr, stdall: log.stdall,
signal: status.signal, retval: rv&0xff, error: error,
status_code: status.code};
ret.unix = error ? -257 :
ret.signal ? -(sigstring_int[ret.signal]||258) : ret.retval;
return opt.out=='retval' ? ret.retval :
opt.out=='unix' ? ret.unix :
opt.out=='stdout' ? ret.stdout :
opt.out=='stderr' ? ret.stderr :
opt.out=='stdall' ? ret.stdall :
ret;
});
E.sys_sync = (cmd, opt)=>{
opt = E.process_opt(opt||{});
cmd = E.get_cmd(cmd, opt);
if (opt.verbose)
console.log(cmd.cmd, cmd.argv.join(' '));
let redir = E.get_redir_sync(opt.redir, opt.stdio);
let opts = assign({stdio: redir.stdio, cwd: opt.cwd}, opt.opt);
opts.env = E.get_env(opt);
let child = child_process.spawnSync(cmd.cmd, cmd.argv, opts);
close_fds(null, redir.fds);
let rv = child.error ? -1 : child.status;
// XXX sergey: stdall must be mixed, not concatinated
let stdout = (child.stdout||'').toString();
let stderr = (child.stderr||'').toString();
let ret = {stdout: stdout, stderr: stderr, stdall: stdout+stderr,
signal: child.signal, retval: rv&0xff, error: child.error,
status_code: child.status};
ret.unix = ret.error ? -257 :
ret.signal ? -(sigstring_int[ret.signal]||258) : ret.retval;
return opt.out=='retval' ? ret.retval :
opt.out=='unix' ? ret.unix :
opt.out=='stdout' ? stdout :
opt.out=='stderr' ? stderr :
opt.out=='stdall' ? stdout+stderr :
ret;
};
let which_cache = {};
E.which = bin=>{
let cache = which_cache;
if (bin in cache && file.is_exec(cache[bin]))
return cache[bin];
let ret = file.which(bin)||'';
let sys_opts = {out: 'stdout'};
if (ret&&file.is_win)
ret = string.chomp(E.sys_sync(['cygpath', '-w', ret], sys_opts));
if (ret&&file.is_win&&file.is_symlink(ret))
ret = fs.readlinkSync(ret);
cache[bin] = ret;
return ret;
};
E.get = (cmd, opt)=>{
opt = assign(Array.isArray(cmd) ? {} :
{shell: true, stdio: 'inherit'}, opt);
if (opt.dry_run)
{
if (opt.verbose)
console.log(array.to_array(cmd).join(' '));
return;
}
return E.sys_sync(cmd, opt);
};
E.get_lines = (cmd, opt)=>{
let ret = E.get(cmd, assign({out: 'stdout', stdio: 'pipe'}, opt));
return string.split_crlf(ret);
};
E.get_line = (cmd, opt)=>E.get_lines(cmd, opt)[0];