list-open-files
Version:
lsof for Node.js but with a modern API
282 lines • 8.32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const child_process_1 = require("child_process");
const util_1 = require("util");
const lsof_to_api_1 = require("./lsof-to-api");
const utils_1 = require("../../utils");
const exec = util_1.promisify(child_process_1.exec);
const oneToNineDescription = "1-9 dialect-specific field identifiers (The output\n" +
" of -F? identifies the information to be found\n" +
" in dialect-specific fields.)";
const fields = {
a: {
code: "a".charCodeAt(0),
description: "file access mode r w u(=rw)",
},
c: {
code: "c".charCodeAt(0),
description: "process command name (all characters from proc or user structure)",
},
C: {
code: "C".charCodeAt(0),
description: "file structure share count",
handler: utils_1.toInteger,
},
d: {
code: "d".charCodeAt(0),
description: "file's device character code",
handler: utils_1.toInteger,
},
D: {
code: "D".charCodeAt(0),
description: "file's major/minor device number (0x<hexadecimal>)",
handler: utils_1.toInteger,
},
f: {
code: "f".charCodeAt(0),
description: "file descriptor (always selected)",
},
F: {
code: "F".charCodeAt(0),
description: "file structure address (0x<hexadecimal>)",
handler: utils_1.toInteger,
},
G: {
code: "G".charCodeAt(0),
description: "file flaGs (0x<hexadecimal>; names if +fg follows)",
},
g: {
code: "g".charCodeAt(0),
description: "process group ID",
handler: utils_1.toInteger,
},
i: {
code: "i".charCodeAt(0),
description: "file's inode number",
},
K: {
code: "K".charCodeAt(0),
description: "tasK ID",
handler: utils_1.toInteger,
},
k: {
code: "k".charCodeAt(0),
description: "link count",
handler: utils_1.toInteger,
},
l: {
code: "l".charCodeAt(0),
description: "file's lock status",
},
L: {
code: "L".charCodeAt(0),
description: "process login name",
},
n: {
code: "n".charCodeAt(0),
description: "file name, comment, Internet address",
},
N: {
code: "N".charCodeAt(0),
description: "node identifier (ox<hexadecimal>",
},
o: {
code: "o".charCodeAt(0),
description: "file's offset (decimal)",
handler: utils_1.toInteger,
},
p: {
code: "p".charCodeAt(0),
description: "process ID (always selected)",
},
P: {
code: "P".charCodeAt(0),
description: "protocol name",
},
r: {
code: "r".charCodeAt(0),
description: "raw device number (0x<hexadecimal>)",
handler: utils_1.toInteger,
},
R: {
code: "R".charCodeAt(0),
description: "parent process ID",
handler: utils_1.toInteger,
},
s: {
code: "s".charCodeAt(0),
description: "file's size (decimal)",
handler: utils_1.toInteger,
},
S: {
code: "S".charCodeAt(0),
description: "file's stream identification",
},
t: {
code: "t".charCodeAt(0),
description: "file's type",
},
T: {
code: "T".charCodeAt(0),
description: "TCP/TPI information, identified by prefixes (the '='" +
" is part of the prefix):\n" +
"QR=<read queue size>\n" +
"QS=<send queue size>\n" +
"SO=<socket options and values> (not all dialects)\n" +
"SS=<socket states> (not all dialects)\n" +
"ST=<connection state>\n" +
"TF=<TCP flags and values> (not all dialects)\n" +
"WR=<window read size> (not all dialects)\n" +
"WW=<window write size> (not all dialects)\n" +
"(TCP/TPI information isn't reported for all supported\n" +
"UNIX dialects. The -h or -? help output for the\n" +
"-T option will show what TCP/TPI reporting can be\n" +
"requested.)",
},
u: {
code: "u".charCodeAt(0),
description: "process user ID",
},
z: {
code: "z".charCodeAt(0),
description: "Solaris 10 and higher zone name",
},
Z: {
code: "Z".charCodeAt(0),
description: "SELinux security context (inhibited when SELinux is disabled)",
},
1: {
code: "1".charCodeAt(0),
description: oneToNineDescription,
},
2: {
code: "2".charCodeAt(0),
description: oneToNineDescription,
},
3: {
code: "3".charCodeAt(0),
description: oneToNineDescription,
},
4: {
code: "4".charCodeAt(0),
description: oneToNineDescription,
},
5: {
code: "5".charCodeAt(0),
description: oneToNineDescription,
},
6: {
code: "6".charCodeAt(0),
description: oneToNineDescription,
},
7: {
code: "7".charCodeAt(0),
description: oneToNineDescription,
},
8: {
code: "8".charCodeAt(0),
description: oneToNineDescription,
},
9: {
code: "9".charCodeAt(0),
description: oneToNineDescription,
},
"\n": {
code: "\n".charCodeAt(0),
description: "New file delimiter",
},
};
const fieldMap = {};
Object.keys(fields).forEach(key => {
const field = fields[key];
const code = field.code;
fieldMap[code] = key;
});
function getParts(buffer) {
const parts = [];
let pos = 0;
while (true) {
const end = buffer.indexOf(0, pos);
if (end === -1) {
parts.push(buffer.slice(pos));
break;
}
else {
parts.push(buffer.slice(pos, end));
pos = end + 1;
}
}
return parts;
}
function parseParts(parts) {
const processes = [];
let cur;
const parsePart = (part) => {
const fieldCode = part[0];
const fieldKey = fieldMap[fieldCode];
if (!fieldKey)
throw new Error(`Invalid field in reponse from 'lsof': ${part}`);
if (fieldKey === 'p') {
// New process
cur = {
proc: {
p: utils_1.toInteger(part.slice(1).toString()),
},
files: [],
};
processes.push(cur);
return;
}
if (!cur)
throw new Error("Invalid response from 'lsof', missing pid");
if (fieldKey === "\n") {
cur.files.push({});
parsePart(part.slice(1));
return;
}
// Regular field, generic parsing
const field = fieldKey;
const rawValue = part.slice(1).toString();
const fieldInfo = fields[field];
const value = fieldInfo && fieldInfo.handler
? fieldInfo.handler(rawValue)
: rawValue;
if (cur.files.length > 0)
// File part
cur.files[cur.files.length - 1][field] =
value;
else
// Process part
cur.proc[field] = value;
};
parts =
parts.length > 0
&& parts[parts.length - 1].length === 1
&& parts[parts.length - 1][0] === 10
? parts.slice(0, parts.length - 1)
: parts;
parts.forEach(parsePart);
return processes;
}
async function invoke(pids) {
const { stdout } = await exec(`lsof -M -P -n -F 0 +fG -p ${pids.join(',')}`, { encoding: "buffer" });
return stdout;
}
async function lsofAndTransform(pids) {
const buf = await invoke(pids);
const parts = getParts(buf);
const rawResult = parseParts(parts);
return lsof_to_api_1.transformParts(rawResult);
}
async function lsof(pids, concurrency) {
const nchunks = Math.max(Math.min(concurrency, pids.length), 1);
const chunks = [];
const chunksize = pids.length / nchunks;
for (var i = 0; i < nchunks; ++i) {
chunks.push(pids.slice(i * chunksize, (i + 1) * chunksize));
}
const chunkedProcesses = await Promise.all(chunks.map(pids => lsofAndTransform(pids)));
return [].concat(...chunkedProcesses);
}
exports.lsof = lsof;
//# sourceMappingURL=index.js.map