blob: eb1ac9e4430aea3caa0ee878dbbbf9e6c570741b [file] [log] [blame]
const { spawn } = require('child_process');
const targz = require('targz');
const DBUS_MONITOR_LEGACY =
'dbus-monitor --system | grep "sendMessage\\|ReceivedMessage" -A7 \n';
const DBUS_MONITOR_NEW =
'dbus-monitor --system | grep "member=execute\\|method return" -A7 \n';
// Capture state for all scripts
var g_capture_state = 'not started';
var g_capture_mode = 'live';
// For capturing IPMI requests live
var g_dbus_monitor_cmd = '';
// For tracking transfer
var g_hexdump = '';
var g_hexdump_received_size = 0;
var g_hexdump_total_size = 0;
function currTimestamp() {
var tmp = new Date();
return (tmp.getTime() + tmp.getTimezoneOffset() * 60000) / 1000;
}
var g_child;
var g_rz;
var g_capture_live = true;
var g_dbus_capture_tarfile_size = 0;
function ParseHexDump(hd) {
let ret = [];
let lines = hd.split('\n');
let tot_size = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trimEnd();
const sp = line.split(' ');
if (line.length < 1) continue;
if (sp.length < 1) continue;
for (let j = 1; j < sp.length; j++) {
let b0 = sp[j].slice(2);
let b1 = sp[j].slice(0, 2);
b0 = parseInt(b0, 16);
b1 = parseInt(b1, 16);
ret.push(b0);
ret.push(b1);
}
console.log('[' + line + ']')
{
tot_size = parseInt(sp[0], 16);
console.log('File size: ' + tot_size + ' ' + sp[0]);
}
}
ret = ret.slice(0, tot_size);
return new Buffer(ret);
}
function SaveHexdumpToFile(hd, file_name) {
const buf = ParseHexDump(hd);
fs.writeFileSync(file_name, buf)
}
// Delimiters: ">>>>>>" and "<<<<<<"
function ExtractMyDelimitedStuff(x, parse_as = undefined) {
let i0 = x.lastIndexOf('>>>>>>'), i1 = x.lastIndexOf('<<<<<<');
if (i0 != -1 && i1 != -1) {
let ret = x.substr(i0 + 6, i1 - i0 - 6);
if (parse_as == undefined)
return ret;
else if (parse_as == 'int')
return parseInt(ret);
} else
return null;
}
function streamWrite(stream, chunk, encoding = 'utf8') {
return new Promise((resolve, reject) => {
const errListener = (err) => {
stream.removeListener('error', errListener);
reject(err);
};
stream.addListener('error', errListener);
const callback = () => {
stream.removeListener('error', errListener);
resolve(undefined);
};
stream.write(chunk, encoding, callback);
});
}
function ExtractTarFile() {
const tar_file = 'DBUS_MONITOR.tar.gz';
const target = '.';
targz.decompress({src: tar_file, dest: target}, function(err) {
if (err) {
console.log('Error decompressing .tar.gz file:' + err);
}
// Attempt to load even if error occurs
// example error: "Error decompressing .tar.gz file:Error: incorrect data check"
console.log('Done! will load file contents');
if (g_capture_mode == 'staged') {
fs.readFile('./DBUS_MONITOR', {encoding: 'utf-8'}, (err, data) => {
if (err) {
console.log('Error in readFile: ' + err);
} else {
ParseIPMIDump(data);
}
});
} else if (g_capture_mode == 'staged2') {
OpenDBusPcapFile('./DBUS_MONITOR');
}
});
}
function OnCaptureStart() {
switch (g_capture_state) {
case 'not started':
capture_info.textContent = 'dbus-monitor running on BMC';
break;
default:
break;
}
}
function OnCaptureStop() {
btn_start_capture.disabled = false;
select_capture_mode.disabled = false;
text_hostname.disabled = false;
g_capture_state = 'not started';
}
async function OnTransferCompleted() {
setTimeout(function() {
console.log('OnTransferCompleted');
g_child.kill('SIGINT');
}, 5000);
capture_info.textContent = 'Loaded the capture file';
OnCaptureStop();
ExtractTarFile();
}
// Example output from stderr:
// ^M Bytes received: 2549/ 2549 BPS:6370
async function LaunchRZ() {
// On the Host
// Remove existing file
const file_names = ['DBUS_MONITOR', 'DBUS_MONITOR.tar.gz'];
try {
for (let i = 0; i < 2; i++) {
const fn = file_names[i];
if (fs.existsSync(fn)) {
fs.unlinkSync(fn); // unlink is basically rm
console.log('Removed file: ' + fn);
}
}
} catch (err) {
}
g_rz = spawn(
'screen', ['rz', '-a', '-e', '-E', '-r', '-w', '32767'], {shell: false});
g_rz.stdout.on('data', (data) => {
console.log('[rz] received ' + data.length + ' B');
console.log(data);
console.log(data + '');
// data = MyCorrection(data);
if (data != undefined) g_child.stdin.write(data);
});
g_rz.stderr.on('data', (data) => {
console.log('[rz] error: ' + data);
let s = data.toString();
let idx = s.lastIndexOf('Bytes received:');
if (idx != -1) {
capture_info.textContent = s.substr(idx);
}
if (data.indexOf('Transfer complete') != -1) {
OnTransferCompleted();
} else if (data.indexOf('Transfer incomplete') != -1) {
// todo: retry transfer
// Bug info
// Uncaught Error [ERR_STREAM_WRITE_AFTER_END]: write after end
// at writeAfterEnd (_stream_writable.js:253)
// at Socket.Writable.write (_stream_writable.js:302)
// at Socket.<anonymous> (ipmi_capture.js:317)
// at Socket.emit (events.js:210)
// at addChunk (_stream_readable.js:308)
// at readableAddChunk (_stream_readable.js:289)
// at Socket.Readable.push (_stream_readable.js:223)
// at Pipe.onStreamRead (internal/stream_base_commons.js:182)
capture_info.textContent = 'Transfer incomplete';
}
});
await Promise.all(
[g_rz.stdin.pipe(g_child.stdout), g_rz.stdout.pipe(g_child.stdin)]);
}
function ClearAllPendingTimeouts() {
var id = setTimeout(function() {}, 0);
for (; id >= 0; id--) clearTimeout(id);
}
function StartDbusMonitorFileSizePollLoop() {
QueueDbusMonitorFileSize(5);
}
function QueueDbusMonitorFileSize(secs = 5) {
setTimeout(function() {
g_child.stdin.write(
'a=`ls -l /run/initramfs/DBUS_MONITOR | awk \'{print $5}\'` ; echo ">>>>>>$a<<<<<<" \n\n\n\n');
QueueDbusMonitorFileSize(secs);
}, secs * 1000);
}
function StopCapture() {
switch (g_capture_mode) {
case 'live':
g_child.stdin.write('\x03 ');
g_capture_state = 'stopping';
capture_info.textContent = 'Ctrl+C sent to BMC console';
break;
case 'staged':
ClearAllPendingTimeouts();
g_child.stdin.write(
'echo ">>>>>>" && killall busctl && echo "<<<<<<" \n\n\n\n');
g_capture_state = 'stopping';
capture_info.textContent = 'Stopping dbus-monitor';
case 'staged2':
g_hexdump_received_size = 0;
g_hexdump_total_size = 0;
ClearAllPendingTimeouts();
g_child.stdin.write(
'echo ">>>>>>" && killall busctl && echo "<<<<<<" \n\n\n\n');
g_capture_state = 'stopping';
capture_info.textContent = 'Stopping busctl';
break;
}
}
function QueueBMCConsoleHello(secs = 3) {
setTimeout(function() {
try {
if (g_capture_state == 'not started') {
console.log('Sending hello <cr> to the BMC');
g_child.stdin.write('\n');
QueueBMCConsoleHello(secs);
}
} catch (err) {
console.log('g_child may have ended as intended');
}
}, secs * 1000);
}
// The command line needed to access the BMC. The expectation is
// executing this command brings the user to the BMC's console.
function GetCMDLine() {
let v = text_hostname.value.split(' ');
return [v[0], v.slice(1, v.length)];
}
async function StartCapture(host) {
// Disable buttons
HideWelcomeScreen();
ShowIPMITimeline();
ShowNavigation();
let args = GetCMDLine();
btn_start_capture.disabled = true;
select_capture_mode.disabled = true;
text_hostname.disabled = true;
capture_info.textContent = 'Contacting BMC console: ' + args.toString();
// On the B.M.C.
let last_t = currTimestamp();
let attempt = 0;
console.log('Args: ' + args);
g_child = spawn(args[0], args[1], {shell: true});
g_child.stdout.on('data', async function(data) {
QueueBMCConsoleHello();
var t = currTimestamp();
{
switch (g_capture_state) {
case 'not started': // Do nothing
break;
case 'started':
attempt++;
console.log('attempt ' + attempt);
g_child.stdin.write('echo "haha" \n');
await streamWrite(g_child.stdin, 'whoami \n');
let idx = data.indexOf('haha');
if (idx != -1) {
ClearAllPendingTimeouts();
OnCaptureStart(); // Successfully logged on, start
if (g_capture_mode == 'live') {
g_child.stdin.write(
'\n\n' +
'a=`pidof btbridged`;b=`pidof kcsbridged`;c=`pidof netipmid`;' +
'echo ">>>>>>$a,$b,$c<<<<<<"\n\n');
g_capture_state = 'determine bridge daemon';
} else {
g_capture_state = 'dbus monitor start';
}
capture_info.textContent = 'Reached BMC console';
} else {
console.log('idx=' + idx);
}
break;
case 'determine bridge daemon': {
const abc = ExtractMyDelimitedStuff(data.toString());
if (abc == null) break;
const sp = abc.split(',');
if (parseInt(sp[0]) >= 0) { // btbridged, legacy interface
g_dbus_monitor_cmd = DBUS_MONITOR_LEGACY;
console.log('The BMC is using btbridged.');
} else if (parseInt(sp[1]) >= 0) { // new iface
g_dbus_monitor_cmd = DBUS_MONITOR_NEW;
console.log('The BMC is using kcsbridged.');
} else if (parseInt(sp[2]) >= 0) {
g_dbus_monitor_cmd = DBUS_MONITOR_NEW;
console.log('The BMC is using netipmid.');
} else {
console.log('Cannot determine the IPMI bridge daemon\n')
return;
}
g_capture_state = 'dbus monitor start';
break;
}
case 'dbus monitor start':
if (g_capture_mode == 'live') {
// It would be good to make sure the console bit rate is greater
// than the speed at which outputs are generated.
// g_child.stdin.write("dbus-monitor --system | grep
// \"sendMessage\\|ReceivedMessage\" -A7 \n")
ClearAllPendingTimeouts();
g_child.stdin.write(
'dbus-monitor --system | grep "member=execute\\|method return" -A7 \n');
capture_info.textContent = 'Started dbus-monitor for live capture';
} else {
// g_child.stdin.write("dbus-monitor --system | grep
// \"sendMessage\\|ReceivedMessage\" -A7 >
// /run/initramfs/DBUS_MONITOR & \n\n\n")
ClearAllPendingTimeouts();
if (g_capture_mode == 'staged') {
g_child.stdin.write(
'dbus-monitor --system > /run/initramfs/DBUS_MONITOR & \n\n\n');
capture_info.textContent =
'Started dbus-monitor for staged IPMI capture';
} else if (g_capture_mode == 'staged2') {
g_child.stdin.write(
'busctl capture > /run/initramfs/DBUS_MONITOR & \n\n\n');
capture_info.textContent =
'Started busctl for staged IPMI + DBus capture';
}
StartDbusMonitorFileSizePollLoop();
}
g_capture_state = 'dbus monitor running';
break;
case 'dbus monitor running':
if (g_capture_mode == 'staged' || g_capture_mode == 'staged2') {
let s = data.toString();
let tmp = ExtractMyDelimitedStuff(s, 'int');
if (tmp != undefined) {
let sz = Math.floor(parseInt(tmp) / 1024);
if (!isNaN(sz)) {
capture_info.textContent =
'Raw Dbus capture size: ' + sz + ' KiB';
} else { // This can happen if the output is cut by half & may be
// fixed by queuing console outputs
}
}
} else {
AppendToParseBuffer(data.toString());
MunchLines();
UpdateLayout();
ComputeHistogram();
}
break;
case 'dbus monitor end': // Todo: add speed check
let s = data.toString();
let i0 = s.lastIndexOf('>>>>'), i1 = s.lastIndexOf('<<<<');
if (i0 != -1 && i1 != -1) {
let tmp = s.substr(i0 + 4, i1 - i0 - 4);
let sz = parseInt(tmp);
if (isNaN(sz)) {
console.log(
'Error: the tentative dbus-profile dump is not found!');
} else {
let bps = sz / 10;
console.log('dbus-monitor generates ' + bps + 'B per second');
}
}
g_child.kill('SIGINT');
break;
case 'sz sending':
console.log('[sz] Received a chunk of size ' + data.length);
console.log(data);
console.log(data + '');
// capture_info.textContent = "Received a chunk of size " +
// data.length
g_rz.stdin.write(data);
break;
case 'stopping':
let t = data.toString();
if (g_capture_mode == 'live') {
if (t.lastIndexOf('^C') != -1) {
// Live mode
g_child.kill('SIGINT');
g_capture_state = 'not started';
OnCaptureStop();
capture_info.textContent = 'connection to BMC closed';
// Log mode
}
} else if (
g_capture_mode == 'staged' || g_capture_mode == 'staged2') {
ClearAllPendingTimeouts();
if (t.lastIndexOf('<<<<<<') != -1) {
g_capture_state = 'compressing';
g_child.stdin.write(
'echo ">>>>>>" && cd /run/initramfs && tar cfz DBUS_MONITOR.tar.gz DBUS_MONITOR && echo "<<<<<<" \n\n\n\n');
capture_info.textContent = 'Compressing dbus monitor dump on BMC';
}
}
break;
case 'compressing':
g_child.stdin.write(
'\n\na=`ls -l /run/initramfs/DBUS_MONITOR.tar.gz | awk \'{print $5}\'` && echo ">>>>>>$a<<<<<<" \n\n\n\n');
g_capture_state = 'dbus_monitor size';
capture_info.textContent = 'Obtaining size of compressed dbus dump';
break;
case 'dbus_monitor size':
// Starting RZ
let tmp = ExtractMyDelimitedStuff(data.toString(), 'int');
if (tmp != null && !isNaN(tmp)) { // Wait until result shows up
g_hexdump_total_size = tmp;
console.log(
'dbus_monitor size tmp=' + tmp + ', ' + data.toString());
// if (tmp != undefined) {
// g_dbus_capture_tarfile_size = tmp;
// capture_info.textContent =
// 'Starting rz and sz, file size: ' + Math.floor(tmp / 1024)
// + ' KiB';
// } else {
// capture_info.textContent = 'Starting rz and sz';
// }
// g_capture_state = 'sz start';
// g_child.stdin.write(
// '\n\n\n\n' +
// 'sz -a -e -R -L 512 -w 32767 -y
// /run/initramfs/DBUS_MONITOR.tar.gz\n');
// g_capture_state = 'sz sending';
// LaunchRZ();
g_child.stdin.write(
'echo ">>>>>>"; hexdump /run/initramfs/DBUS_MONITOR.tar.gz ; echo "<<<<<<"; \n');
g_capture_state = 'test hexdump running';
g_hexdump = new Buffer([]);
}
break;
case 'test hexdump start':
g_child.stdin.write(
'echo ">>>>>>"; hexdump /run/initramfs/DBUS_MONITOR.tar.gz ; echo "<<<<<<"; \n');
g_capture_state = 'test hexdump running';
g_hexdump = new Buffer([]);
g_hexdump_received_size = 0;
break;
case 'test hexdump running':
g_hexdump += data;
const lines = data.toString().split('\n');
for (let j = lines.length - 1; j >= 0; j--) {
sp = lines[j].trimEnd().split(' ');
if (sp.length >= 1) {
const sz = parseInt(sp[0], 16)
if (!isNaN(sz)) {
if (g_hexdump_received_size < sz) {
g_hexdump_received_size = sz;
capture_info.textContent = 'Receiving capture file: ' + sz +
' / ' + g_hexdump_total_size + ' B';
break;
}
}
}
}
if (data.includes('<<<<<<') && !data.includes('echo')) {
g_hexdump = ExtractMyDelimitedStuff(g_hexdump);
SaveHexdumpToFile(g_hexdump, 'DBUS_MONITOR.tar.gz');
OnTransferCompleted();
}
break;
}
last_t = t;
}
});
g_child.stderr.on('data', (data) => {
console.log('[bmc] err=' + data);
g_child.stdin.write('echo "haha" \n\n');
});
g_child.on('close', (code) => {
console.log('return code: ' + code);
});
}