1632 lines
46 KiB
JavaScript
1632 lines
46 KiB
JavaScript
const events = require("events");
|
|
const cp = require("child_process");
|
|
|
|
class Zetabyte {
|
|
constructor(options = {}) {
|
|
const zb = this;
|
|
zb.options = options;
|
|
|
|
options.paths = options.paths || {};
|
|
if (!options.paths.zpool) {
|
|
options.paths.zpool = "/sbin/zpool";
|
|
}
|
|
|
|
if (!options.paths.zfs) {
|
|
options.paths.zfs = "/sbin/zfs";
|
|
}
|
|
|
|
if (!options.paths.sudo) {
|
|
options.paths.sudo = "/usr/bin/sudo";
|
|
}
|
|
|
|
if (!options.paths.chroot) {
|
|
options.paths.chroot = "/usr/sbin/chroot";
|
|
}
|
|
|
|
if (!options.timeout) {
|
|
options.timeout = 10 * 60 * 1000;
|
|
}
|
|
|
|
if (!options.executor) {
|
|
options.executor = {
|
|
spawn: cp.spawn,
|
|
};
|
|
}
|
|
|
|
zb.DEFAULT_ZPOOL_LIST_PROPERTIES = [
|
|
"name",
|
|
"size",
|
|
"allocated",
|
|
"free",
|
|
"cap",
|
|
"health",
|
|
"altroot",
|
|
];
|
|
|
|
zb.DEFAULT_ZFS_LIST_PROPERTIES = [
|
|
"name",
|
|
"used",
|
|
"avail",
|
|
"refer",
|
|
"type",
|
|
"mountpoint",
|
|
];
|
|
|
|
zb.helpers = {
|
|
zfsErrorStr: function (error, stderr) {
|
|
if (!error) return null;
|
|
|
|
if (error.killed) return "Process killed due to timeout.";
|
|
|
|
return error.message || (stderr ? stderr.toString() : "");
|
|
},
|
|
|
|
zfsError: function (error, stderr) {
|
|
return new Error(zb.helpers.zfsErrorStr(error, stderr));
|
|
},
|
|
|
|
parseTabSeperatedTable: function (data) {
|
|
if (!data) {
|
|
return [];
|
|
}
|
|
|
|
const lines = data.trim().split("\n");
|
|
const rows = [];
|
|
|
|
for (let i = 0, numLines = lines.length; i < numLines; i++) {
|
|
if (lines[i]) {
|
|
rows.push(lines[i].split("\t"));
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
},
|
|
|
|
/*
|
|
* Parse the output of `zfs get ...`, invoked by zfs.get below. The output has
|
|
* the form:
|
|
*
|
|
* <dataset name> <property name> <property value>
|
|
*
|
|
* and those fields are tab-separated.
|
|
*/
|
|
parsePropertyList: function (data) {
|
|
if (!data) {
|
|
return {};
|
|
}
|
|
|
|
const lines = data.trim().split("\n");
|
|
const properties = {};
|
|
|
|
lines.forEach(function (line) {
|
|
const fields = line.split("\t");
|
|
if (!properties[fields[0]]) properties[fields[0]] = {};
|
|
properties[fields[0]][fields[1]] = {
|
|
value: fields[2],
|
|
received: fields[3],
|
|
source: fields[4],
|
|
};
|
|
});
|
|
|
|
return properties;
|
|
},
|
|
|
|
listTableToPropertyList: function (properties, data) {
|
|
const entries = [];
|
|
data.forEach((row) => {
|
|
let entry = {};
|
|
properties.forEach((value, index) => {
|
|
entry[value] = row[index];
|
|
});
|
|
entries.push(entry);
|
|
});
|
|
|
|
return entries;
|
|
},
|
|
|
|
extractSnapshotName: function (datasetName) {
|
|
return datasetName.substring(datasetName.indexOf("@") + 1);
|
|
},
|
|
|
|
extractDatasetName: function (datasetName) {
|
|
if (datasetName.includes("@")) {
|
|
return datasetName.substring(0, datasetName.indexOf("@"));
|
|
}
|
|
|
|
return datasetName;
|
|
},
|
|
|
|
isZfsSnapshot: function (snapshotName) {
|
|
return snapshotName.includes("@");
|
|
},
|
|
|
|
extractPool: function (datasetName) {
|
|
const parts = datasetName.split("/");
|
|
return parts[0];
|
|
},
|
|
|
|
extractParentDatasetName: function (datasetName) {
|
|
const parts = datasetName.split("/");
|
|
parts.pop();
|
|
return parts.join("/");
|
|
},
|
|
|
|
extractLeafName: function (datasetName) {
|
|
return datasetName.split("/").pop();
|
|
},
|
|
|
|
isPropertyValueSet: function (value) {
|
|
if (
|
|
value === undefined ||
|
|
value === null ||
|
|
value == "" ||
|
|
value == "-"
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
generateZvolSize: function (capacity_bytes, block_size) {
|
|
block_size = "" + block_size;
|
|
block_size = block_size.toLowerCase();
|
|
switch (block_size) {
|
|
case "512":
|
|
block_size = 512;
|
|
break;
|
|
case "1024":
|
|
case "1k":
|
|
block_size = 1024;
|
|
break;
|
|
case "2048":
|
|
case "2k":
|
|
block_size = 2048;
|
|
break;
|
|
case "4096":
|
|
case "4k":
|
|
block_size = 4096;
|
|
break;
|
|
case "8192":
|
|
case "8k":
|
|
block_size = 8192;
|
|
break;
|
|
case "16384":
|
|
case "16k":
|
|
block_size = 16384;
|
|
break;
|
|
case "32768":
|
|
case "32k":
|
|
block_size = 32768;
|
|
break;
|
|
case "65536":
|
|
case "64k":
|
|
block_size = 65536;
|
|
break;
|
|
case "131072":
|
|
case "128k":
|
|
block_size = 131072;
|
|
break;
|
|
}
|
|
|
|
capacity_bytes = Number(capacity_bytes);
|
|
let result = block_size * Math.round(capacity_bytes / block_size);
|
|
if (result < capacity_bytes)
|
|
result = Number(result) + Number(block_size);
|
|
|
|
return result;
|
|
},
|
|
};
|
|
|
|
zb.zpool = {
|
|
/**
|
|
* zpool add [-fn] pool vdev ...
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} vdevs
|
|
*/
|
|
add: function (pool, vdevs) {
|
|
// -f force
|
|
// -n noop
|
|
},
|
|
|
|
/**
|
|
* zpool attach [-f] pool device new_device
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} device
|
|
* @param {*} new_device
|
|
*/
|
|
attach: function (pool, device, new_device) {
|
|
// -f Forces use of new_device, even if its appears to be in use.
|
|
},
|
|
|
|
/**
|
|
* zpool checkpoint [-d, --discard] pool
|
|
*
|
|
* @param {*} pool
|
|
*/
|
|
checkpoint: function (pool) {},
|
|
|
|
/**
|
|
* zpool clear [-F [-n]] pool [device]
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} device
|
|
*/
|
|
clear: function (pool, device) {},
|
|
|
|
/**
|
|
* zpool create [-fnd] [-o property=value] ... [-O
|
|
* file-system-property=value] ... [-m mountpoint] [-R root] [-t
|
|
* tempname] pool vdev ...
|
|
*
|
|
* This allows fine-grained control and exposes all features of the
|
|
* zpool create command, including log devices, cache devices, and hot spares.
|
|
* The input is an object of the form produced by the disklayout library.
|
|
*/
|
|
create: function (pool, options) {
|
|
if (arguments.length != 2)
|
|
throw Error("Invalid arguments, 2 arguments required");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("create");
|
|
|
|
if (options.force) args.push("-f");
|
|
if (options.noop) args.push("-n");
|
|
if (options.disableFeatures) args.push("-d");
|
|
if (options.properties) {
|
|
for (const [key, value] of Object.entries(options.properties)) {
|
|
args.push("-o");
|
|
args.push(`${key}=${value}`);
|
|
}
|
|
}
|
|
if (options.fsProperties) {
|
|
for (const [key, value] of Object.entries(options.fsProperties)) {
|
|
args.push("-O");
|
|
args.push(`${key}=${value}`);
|
|
}
|
|
}
|
|
if (options.mountpoint)
|
|
args = args.concat(["-m", options.mountpoint]);
|
|
if (options.root) args = args.concat(["-R", options.root]);
|
|
if (options.tempname) args = args.concat(["-t", options.tempname]);
|
|
|
|
args.push(pool);
|
|
options.vdevs.forEach(function (vdev) {
|
|
if (vdev.type) args.push(vdev.type);
|
|
if (vdev.devices) {
|
|
vdev.devices.forEach(function (dev) {
|
|
args.push(dev.name);
|
|
});
|
|
} else {
|
|
args.push(vdev.name);
|
|
}
|
|
});
|
|
|
|
if (options.spares) {
|
|
args.push("spare");
|
|
options.spares.forEach(function (dev) {
|
|
args.push(dev.name);
|
|
});
|
|
}
|
|
|
|
if (options.logs) {
|
|
args.push("log");
|
|
options.logs.forEach(function (dev) {
|
|
args.push(dev.name);
|
|
});
|
|
}
|
|
|
|
if (options.cache) {
|
|
args.push("cache");
|
|
options.cache.forEach(function (dev) {
|
|
args.push(dev.name);
|
|
});
|
|
}
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool destroy [-f] pool
|
|
*
|
|
* @param {*} pool
|
|
*/
|
|
destroy: function (pool) {
|
|
if (arguments.length != 1) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("destroy");
|
|
if (options.force) args.push("-f");
|
|
args.push(pool);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool detach pool device
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} device
|
|
*/
|
|
detach: function (pool, device) {
|
|
if (arguments.length != 2) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("detach");
|
|
args.push(pool);
|
|
args.push(device);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool export [-f] pool ...
|
|
*
|
|
* @param {*} pool
|
|
*/
|
|
export: function (pool) {
|
|
if (arguments.length != 2) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("export");
|
|
if (options.force) args.push("-f");
|
|
if (Array.isArray(pool)) {
|
|
pool.forEach((item) => {
|
|
args.push(item);
|
|
});
|
|
} else {
|
|
args.push(pool);
|
|
}
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool get [-Hp] [-o field[,...]] all | property[,...] pool ...
|
|
*/
|
|
get: function () {},
|
|
|
|
/**
|
|
* zpool history [-il] [pool] ...
|
|
*
|
|
* @param {*} pool
|
|
*/
|
|
history: function (pool) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("history");
|
|
if (options.internal) args.push("-i");
|
|
if (options.longFormat) args.push("-l");
|
|
if (Array.isArray(pool)) {
|
|
pool.forEach((item) => {
|
|
args.push(item);
|
|
});
|
|
} else {
|
|
args.push(pool);
|
|
}
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool import [-d dir | -c cachefile] [-D]
|
|
*
|
|
* zpool import [-o mntopts] [-o property=value] ... [-d dir | -c cachefile]
|
|
* [-D] [-f] [-m] [-N] [-R root] [-F [-n]] -a
|
|
*
|
|
* zpool import [-o mntopts] [-o property=value] ... [-d dir | -c cachefile]
|
|
* [-D] [-f] [-m] [-N] [-R root] [-t] [-F [-n]] pool | id [newpool]
|
|
*
|
|
*
|
|
*
|
|
* @param {*} options
|
|
*/
|
|
import: function (options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("import");
|
|
if (options.dir) args = args.concat(["-d", options.dir]);
|
|
if (options.cachefile) args = args.concat(["-c", options.cachefile]);
|
|
if (options.destroyed) args.push("-D");
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool iostat [-T d|u] [-v] [pool] ... [interval [count]]
|
|
*
|
|
* @param {*} options
|
|
*/
|
|
iostat: function (options = {}) {},
|
|
|
|
/**
|
|
* zpool labelclear [-f] device
|
|
*
|
|
* @param {*} device
|
|
*/
|
|
labelclear: function (device) {},
|
|
|
|
/**
|
|
* zpool list [-Hpv] [-o property[,...]] [-T d|u] [pool] ... [inverval
|
|
* [count]]
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} options
|
|
*/
|
|
list: function (pool, properties, options = {}) {
|
|
if (!(arguments.length >= 1)) throw Error("Invalid arguments");
|
|
if (!properties) properties = zb.DEFAULT_ZPOOL_LIST_PROPERTIES;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("list");
|
|
if (!("parse" in options)) options.parse = true;
|
|
if (!("parseable" in options)) options.parsable = true;
|
|
if (options.parseable || options.parse) args.push("-Hp");
|
|
if (options.verbose) args.push("-v");
|
|
if (properties) {
|
|
if (Array.isArray(properties)) {
|
|
if (properties.length == 0) {
|
|
properties = zb.DEFAULT_ZPOOL_LIST_PROPERTIES;
|
|
}
|
|
args.push("-o");
|
|
args.push(properties.join(","));
|
|
} else {
|
|
args.push("-o");
|
|
args.push(properties);
|
|
}
|
|
}
|
|
if (options.timestamp) args = args.concat(["-T", options.timestamp]);
|
|
if (pool) {
|
|
if (Array.isArray(pool)) {
|
|
pool.forEach((item) => {
|
|
args.push(item);
|
|
});
|
|
} else {
|
|
args.push(pool);
|
|
}
|
|
}
|
|
if (options.interval) args.push(options.interval);
|
|
if (options.count) args.push(options.count);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
if (options.parse) {
|
|
let data = zb.helpers.parseTabSeperatedTable(stdout);
|
|
let indexed = zb.helpers.listTableToPropertyList(
|
|
properties,
|
|
data
|
|
);
|
|
return resolve({
|
|
properties,
|
|
data,
|
|
indexed,
|
|
});
|
|
}
|
|
return resolve({ properties, data: stdout });
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool offline [-t] pool device ...
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} device
|
|
* @param {*} options
|
|
*/
|
|
offline: function (pool, device, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("offline");
|
|
if (options.temporary) args.push("-t");
|
|
args.push(pool);
|
|
args.push(device);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool online [-e] pool device ...
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} device
|
|
* @param {*} options
|
|
*/
|
|
online: function (pool, device, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("online");
|
|
if (options.expand) args.push("-e");
|
|
args.push(pool);
|
|
args.push(device);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool reguid pool
|
|
*
|
|
* @param {*} pool
|
|
*/
|
|
reguid: function (pool) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("reguid");
|
|
args.push(pool);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool remove [-np] pool device ...
|
|
*
|
|
* zpool remove -s pool
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} device
|
|
*/
|
|
remove: function (pool, device, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("remove");
|
|
if (options.noop) args.push("-n");
|
|
if (options.parsable) args.push("-p");
|
|
if (options.stop) args.push("-s");
|
|
args.push(pool);
|
|
if (device) {
|
|
args.push(device);
|
|
}
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool reopen pool
|
|
*
|
|
* @param {*} pool
|
|
*/
|
|
reopen: function (pool) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("reopen");
|
|
args.push(pool);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool replace [-f] pool device [new_device]
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} device
|
|
* @param {*} new_device
|
|
*/
|
|
replace: function (pool, device, new_device) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("replace");
|
|
if (options.force) args.push("-f");
|
|
args.push(pool);
|
|
args.push(device);
|
|
if (new_device) {
|
|
args.push(new_device);
|
|
}
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool scrub [-s | -p] pool ...
|
|
*
|
|
* @param {*} pool
|
|
*/
|
|
scrub: function (pool) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("scrub");
|
|
if (options.stop) args.push("-s");
|
|
if (options.pause) args.push("-p");
|
|
if (Array.isArray(pool)) {
|
|
pool.forEach((item) => {
|
|
args.push(item);
|
|
});
|
|
} else {
|
|
args.push(pool);
|
|
}
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool set property=value pool
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} property
|
|
* @param {*} value
|
|
*/
|
|
set: function (pool, property, value) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("set");
|
|
args.push(`${property}=${value}`);
|
|
args.push(pool);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool split [-n] [-R altroot] [-o mntopts] [-o property=value] pool
|
|
* newpool [device ...]
|
|
*
|
|
* @param {*} pool
|
|
* @param {*} newpool
|
|
* @param {*} device
|
|
*/
|
|
split: function (pool, newpool, device) {},
|
|
|
|
/**
|
|
* zpool status [-vx] [-T d|u] [pool] ... [interval [count]]
|
|
*/
|
|
status: function (pool, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
if (!("parse" in options)) options.parse = true;
|
|
args.push("status");
|
|
if (options.verbose) args.push("-v");
|
|
if (options.exhibiting) args.push("-x");
|
|
if (options.timestamp) args = args.concat(["-T", options.timestamp]);
|
|
if (pool) {
|
|
if (Array.isArray(pool)) {
|
|
pool.forEach((item) => {
|
|
args.push(item);
|
|
});
|
|
} else {
|
|
args.push(pool);
|
|
}
|
|
}
|
|
if (options.interval) args.push(options.interval);
|
|
if (options.count) args.push(options.count);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (options.parse) {
|
|
stdout = stdout.trim();
|
|
if (error || stdout == "no pools available\n") {
|
|
return resolve("UNKNOWN");
|
|
}
|
|
|
|
const lines = stdout.split("\n");
|
|
for (var i = 0; i < lines.length; i++) {
|
|
if (lines[i].trim().substr(0, 5) === "state") {
|
|
return resolve(lines[i].trim().substr(7));
|
|
}
|
|
}
|
|
return resolve("UNKNOWN");
|
|
} else {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zpool upgrade [-v]
|
|
*
|
|
* zpool upgrade [-V version] -a | pool ...
|
|
*
|
|
* @param {*} pool
|
|
*/
|
|
upgrade: function (pool) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("upgrade");
|
|
if (options.version) args = args.concat(["-V", options.version]);
|
|
if (options.all) args.push("-a");
|
|
if (pool) {
|
|
if (Array.isArray(pool)) {
|
|
pool.forEach((item) => {
|
|
args.push(item);
|
|
});
|
|
} else {
|
|
args.push(pool);
|
|
}
|
|
}
|
|
|
|
zb.exec(
|
|
zb.options.paths.zpool,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(stderr);
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
};
|
|
|
|
zb.zfs = {
|
|
/**
|
|
* zfs create [-pu] [-o property=value]... filesystem
|
|
* zfs create [-ps] [-b blocksize] [-o property=value]... -V size volume
|
|
*
|
|
* @param {*} dataset
|
|
* @param {*} options
|
|
*/
|
|
create: function (dataset, options = {}) {
|
|
if (!(arguments.length >= 1)) throw new (Error("Invalid arguments"))();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const idempotent =
|
|
"idempotent" in options
|
|
? options.idempotent
|
|
: "idempotent" in zb.options
|
|
? zb.options.idempotent
|
|
: false;
|
|
|
|
let args = [];
|
|
args.push("create");
|
|
if (options.parents) args.push("-p");
|
|
if (options.unmounted) args.push("-u");
|
|
if (options.blocksize) args = args.concat(["-b", options.blocksize]);
|
|
if (options.properties) {
|
|
for (const [key, value] of Object.entries(options.properties)) {
|
|
args.push("-o");
|
|
args.push(`${key}=${value}`);
|
|
}
|
|
}
|
|
if (options.size) args = args.concat(["-V", options.size]);
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (
|
|
error &&
|
|
!(idempotent && stderr.includes("dataset already exists"))
|
|
)
|
|
return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs destroy [-fnpRrv] filesystem|volume
|
|
* zfs destroy [-dnpRrv] snapshot[%snapname][,...]
|
|
* zfs destroy filesystem|volume#bookmark
|
|
*
|
|
*
|
|
* @param {*} dataset
|
|
* @param {*} options
|
|
*/
|
|
destroy: function (dataset, options = {}) {
|
|
if (!(arguments.length >= 1)) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const idempotent =
|
|
"idempotent" in options
|
|
? options.idempotent
|
|
: "idempotent" in zb.options
|
|
? zb.options.idempotent
|
|
: false;
|
|
|
|
let args = [];
|
|
args.push("destroy");
|
|
if (!("parseable" in options)) options.parseable = true;
|
|
if (options.recurse) args.push("-r");
|
|
if (options.dependents) args.push("-R");
|
|
if (options.force) args.push("-f");
|
|
if (options.noop) args.push("-n");
|
|
if (options.parseable) args.push("-p");
|
|
if (options.verbose) args.push("-v");
|
|
if (options.defer) args.push("-d");
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (
|
|
error &&
|
|
!(
|
|
idempotent &&
|
|
(stderr.includes("dataset does not exist") ||
|
|
stderr.includes("could not find any snapshots to destroy"))
|
|
)
|
|
)
|
|
return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs snapshot|snap [-r] [-o property=value]...
|
|
* filesystem@snapname|volume@snapname
|
|
* filesystem@snapname|volume@snapname...
|
|
*
|
|
* @param {*} dataset
|
|
* @param {*} options
|
|
*/
|
|
snapshot: function (dataset, options = {}) {
|
|
if (!(arguments.length >= 1)) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const idempotent =
|
|
"idempotent" in options
|
|
? options.idempotent
|
|
: "idempotent" in zb.options
|
|
? zb.options.idempotent
|
|
: false;
|
|
|
|
let args = [];
|
|
args.push("snapshot");
|
|
if (options.recurse) args.push("-r");
|
|
if (options.properties) {
|
|
for (const [key, value] of Object.entries(options.properties)) {
|
|
args.push("-o");
|
|
args.push(`${key}=${value}`);
|
|
}
|
|
}
|
|
if (Array.isArray(dataset)) {
|
|
dataset = dataset.join(" ");
|
|
}
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (
|
|
error &&
|
|
!(idempotent && stderr.includes("dataset already exists"))
|
|
)
|
|
return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs rollback [-rRf] snapshot
|
|
*
|
|
* @param {*} dataset
|
|
* @param {*} options
|
|
*/
|
|
rollback: function (dataset, options = {}) {
|
|
if (!(arguments.length >= 1)) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("rollback");
|
|
if (options.recent) args.push("-r");
|
|
if (options.dependents) args.push("-R");
|
|
if (options.force) args.push("-f");
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
/**
|
|
* cannot rollback to 'foo/bar/baz@foobar': more recent snapshots or bookmarks exist
|
|
* use '-r' to force deletion of the following snapshots and bookmarks:
|
|
*/
|
|
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs clone [-p] [-o property=value]... snapshot filesystem|volume
|
|
*
|
|
* @param {*} snapshot
|
|
* @param {*} dataset
|
|
* @param {*} options
|
|
*/
|
|
clone: function (snapshot, dataset, options = {}) {
|
|
if (!(arguments.length >= 2)) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const idempotent =
|
|
"idempotent" in options
|
|
? options.idempotent
|
|
: "idempotent" in zb.options
|
|
? zb.options.idempotent
|
|
: false;
|
|
|
|
let args = [];
|
|
args.push("clone");
|
|
if (options.parents) args.push("-p");
|
|
if (options.properties) {
|
|
for (const [key, value] of Object.entries(options.properties)) {
|
|
args.push("-o");
|
|
args.push(`${key}=${value}`);
|
|
}
|
|
}
|
|
args.push(snapshot);
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (
|
|
error &&
|
|
!(idempotent && stderr.includes("dataset already exists"))
|
|
)
|
|
return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* /bin/sh -c "zfs send [<send_options>] <source> | zfs receive [<receive_options>] <target>
|
|
*
|
|
* @param {*} source
|
|
* @param {*} send_options
|
|
* @param {*} target
|
|
* @param {*} receive_options
|
|
*/
|
|
send_receive(source, send_options = [], target, receive_options = []) {
|
|
if (arguments.length < 4) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = ["-c"];
|
|
let command = [];
|
|
command = command.concat(["zfs", "send"]);
|
|
command = command.concat(send_options);
|
|
command.push(source);
|
|
|
|
command.push("|");
|
|
|
|
command = command.concat(["zfs", "receive"]);
|
|
command = command.concat(receive_options);
|
|
command.push(target);
|
|
|
|
args.push("'" + command.join(" ") + "'");
|
|
|
|
zb.exec("/bin/sh", args, { timeout: zb.options.timeout }, function (
|
|
error,
|
|
stdout,
|
|
stderr
|
|
) {
|
|
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs promote clone-filesystem
|
|
*
|
|
* @param {*} dataset
|
|
*/
|
|
promote: function (dataset) {
|
|
if (arguments.length != 1) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("promote");
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs rename [-f] filesystem|volume|snapshot filesystem|volume|snapshot
|
|
* zfs rename [-f] -p filesystem|volume filesystem|volume
|
|
* zfs rename -u [-p] filesystem filesystem
|
|
* zfs rename -r snapshot snapshot
|
|
*
|
|
* @param {*} source
|
|
* @param {*} target
|
|
* @param {*} options
|
|
*/
|
|
rename: function (source, target, options = {}) {
|
|
if (!(arguments.length >= 2)) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("rename");
|
|
if (options.parents) args.push("-p");
|
|
if (options.unmounted) args.push("-u");
|
|
if (options.force) args.push("-f");
|
|
if (options.recurse) args.push("-r");
|
|
args.push(source);
|
|
args.push(target);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs list [-r|-d depth] [-Hp] [-o property[,property]...] [-t
|
|
* type[,type]...] [-s property]... [-S property]...
|
|
* filesystem|volume|snapshot...
|
|
*
|
|
* @param {*} dataset
|
|
* @param {*} options
|
|
*/
|
|
list: function (dataset, properties, options = {}) {
|
|
if (!(arguments.length >= 1)) throw Error("Invalid arguments");
|
|
if (!properties) properties = zb.DEFAULT_ZFS_LIST_PROPERTIES;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("list");
|
|
if (!("parse" in options)) options.parse = true;
|
|
if (!("parseable" in options)) options.parsable = true;
|
|
if (options.recurse) args.push("-r");
|
|
if (options.depth) args = args.concat(["-d", options.depth]);
|
|
if (options.parseable || options.parse) args.push("-Hp");
|
|
if (options.types) {
|
|
let types;
|
|
if (Array.isArray(options.types)) {
|
|
types = options.types.join(",");
|
|
} else {
|
|
types = options.types;
|
|
}
|
|
args = args.concat(["-t", types]);
|
|
}
|
|
|
|
if (properties) {
|
|
if (Array.isArray(properties)) {
|
|
if (properties.length == 0) {
|
|
properties = zb.DEFAULT_ZFS_LIST_PROPERTIES;
|
|
}
|
|
args.push("-o");
|
|
args.push(properties.join(","));
|
|
} else {
|
|
args.push("-o");
|
|
args.push(properties);
|
|
}
|
|
}
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
|
if (options.parse) {
|
|
let data = zb.helpers.parseTabSeperatedTable(stdout);
|
|
let indexed = zb.helpers.listTableToPropertyList(
|
|
properties,
|
|
data
|
|
);
|
|
return resolve({
|
|
properties,
|
|
data,
|
|
indexed,
|
|
});
|
|
}
|
|
return resolve({ properties, data: stdout });
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs set property=value [property=value]... filesystem|volume|snapshot
|
|
*
|
|
* @param {*} dataset
|
|
* @param {*} properties
|
|
*/
|
|
set: function (dataset, properties) {
|
|
if (arguments.length != 2) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
if (!Object.keys(properties).length) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
let args = [];
|
|
args.push("set");
|
|
|
|
if (properties) {
|
|
for (const [key, value] of Object.entries(properties)) {
|
|
args.push(`${key}=${value}`);
|
|
}
|
|
}
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs get [-r|-d depth] [-Hp] [-o all | field[,field]...] [-t
|
|
* type[,type]...] [-s source[,source]...] all | property[,property]...
|
|
* filesystem|volume|snapshot|bookmark...
|
|
*
|
|
* -o options: name,property,value,received,source - default name,property,value,source
|
|
* -t options: filesystem, snapshot, volume - default all
|
|
* -s options: local,default,inherited,temporary,received,none - default all
|
|
*
|
|
* @param {*} dataset
|
|
* @param {*} properties
|
|
*/
|
|
get: function (dataset, properties = "all", options = {}) {
|
|
if (!(arguments.length >= 2)) throw Error("Invalid arguments");
|
|
if (!properties) properties = "all";
|
|
if (Array.isArray(properties) && !properties.length > 0)
|
|
properties = "all";
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("get");
|
|
if (!("parse" in options)) options.parse = true;
|
|
if (!("parseable" in options)) options.parsable = true;
|
|
if (options.recurse) args.push("-r");
|
|
if (options.depth) args.concat(["-d", options.depth]);
|
|
if (options.parseable || options.parse) args.push("-Hp");
|
|
if (options.parse)
|
|
args = args.concat([
|
|
"-o",
|
|
["name", "property", "value", "received", "source"],
|
|
]);
|
|
if (options.fields && !options.parse) {
|
|
let fields;
|
|
if (Array.isArray(options.fields)) {
|
|
fields = options.fields.join(",");
|
|
} else {
|
|
fields = options.fields;
|
|
}
|
|
|
|
args = args.concat(["-o", fields]);
|
|
}
|
|
if (options.types) {
|
|
let types;
|
|
if (Array.isArray(options.types)) {
|
|
types = options.types.join(",");
|
|
} else {
|
|
types = options.types;
|
|
}
|
|
args = args.concat(["-t", types]);
|
|
}
|
|
if (options.sources) {
|
|
let sources;
|
|
if (Array.isArray(options.sources)) {
|
|
sources = options.sources.join(",");
|
|
} else {
|
|
sources = options.sources;
|
|
}
|
|
args = args.concat(["-s", sources]);
|
|
}
|
|
|
|
if (properties) {
|
|
if (Array.isArray(properties)) {
|
|
if (properties.length > 0) {
|
|
args.push(properties.join(","));
|
|
} else {
|
|
args.push("all");
|
|
}
|
|
} else {
|
|
args.push(properties);
|
|
}
|
|
} else {
|
|
args.push("all");
|
|
}
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
|
if (options.parse) {
|
|
return resolve(zb.helpers.parsePropertyList(stdout));
|
|
}
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs inherit [-rS] property filesystem|volume|snapshot...
|
|
*
|
|
* @param {*} dataset
|
|
* @param {*} property
|
|
*/
|
|
inherit: function (dataset, property) {
|
|
if (arguments.length != 2) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("inherit");
|
|
if (options.recurse) args.push("-r");
|
|
if (options.received) args.push("-S");
|
|
args.push(property);
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs remap filesystem|volume
|
|
*
|
|
* @param {*} dataset
|
|
*/
|
|
remap: function (dataset) {
|
|
if (arguments.length != 1) throw Error("Invalid arguments");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("remap");
|
|
args.push(dataset);
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* zfs upgrade [-v]
|
|
* zfs upgrade [-r] [-V version] -a | filesystem
|
|
*
|
|
* @param {*} dataset
|
|
*/
|
|
upgrade: function (options = {}, dataset) {
|
|
return new Promise((resolve, reject) => {
|
|
let args = [];
|
|
args.push("upgrade");
|
|
if (options.versions) args.push("-v");
|
|
if (options.recurse) args.push("-r");
|
|
if (options.version) args = args.concat(["-V", options.version]);
|
|
if (options.all) args = args.push("-a");
|
|
if (dataset) {
|
|
args.push(dataset);
|
|
}
|
|
|
|
zb.exec(
|
|
zb.options.paths.zfs,
|
|
args,
|
|
{ timeout: zb.options.timeout },
|
|
function (error, stdout, stderr) {
|
|
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
|
return resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Should be a matching interface for spawn roughly
|
|
*
|
|
*/
|
|
exec() {
|
|
const zb = this;
|
|
let command = arguments[0];
|
|
let args, options, callback, timeout;
|
|
let stdout = "";
|
|
let stderr = "";
|
|
switch (arguments.length) {
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
callback = arguments[arguments.length - 1];
|
|
break;
|
|
case 3:
|
|
callback = arguments[arguments.length - 1];
|
|
args = arguments[arguments.length - 2];
|
|
break;
|
|
case 4:
|
|
callback = arguments[arguments.length - 1];
|
|
options = arguments[arguments.length - 2];
|
|
args = arguments[arguments.length - 3];
|
|
break;
|
|
}
|
|
|
|
if (zb.options.chroot) {
|
|
args = args || [];
|
|
args.unshift(command);
|
|
args.unshift(zb.options.chroot);
|
|
command = zb.options.paths.chroot;
|
|
}
|
|
|
|
if (zb.options.sudo) {
|
|
args = args || [];
|
|
args.unshift(command);
|
|
command = zb.options.paths.sudo;
|
|
}
|
|
|
|
const child = zb.options.executor.spawn(command, args, options);
|
|
|
|
let didTimeout = false;
|
|
if (options && options.timeout) {
|
|
timeout = setTimeout(() => {
|
|
didTimeout = true;
|
|
child.kill(options.killSignal || "SIGTERM");
|
|
}, options.timeout);
|
|
}
|
|
|
|
if (callback) {
|
|
child.stdout.on("data", function (data) {
|
|
stdout = stdout + data;
|
|
});
|
|
|
|
child.stderr.on("data", function (data) {
|
|
stderr = stderr + data;
|
|
});
|
|
|
|
child.on("close", function (error) {
|
|
if (timeout) {
|
|
clearTimeout(timeout);
|
|
}
|
|
if (error) {
|
|
if (didTimeout) {
|
|
error.killed = true;
|
|
}
|
|
callback(zb.helpers.zfsError(error, stderr), stdout, stderr);
|
|
}
|
|
callback(null, stdout, stderr);
|
|
});
|
|
}
|
|
|
|
return child;
|
|
}
|
|
}
|
|
exports.Zetabyte = Zetabyte;
|
|
|
|
class ZfsSshProcessManager {
|
|
constructor(client) {
|
|
this.client = client;
|
|
}
|
|
|
|
/**
|
|
* Build a command line from the name and given args
|
|
* TODO: escape the arguments
|
|
*
|
|
* @param {*} name
|
|
* @param {*} args
|
|
*/
|
|
buildCommand(name, args = []) {
|
|
args.unshift(name);
|
|
return args.join(" ");
|
|
}
|
|
|
|
/**
|
|
* https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
|
|
*
|
|
* should return something similar to a child_process that handles the following:
|
|
* - child.stdout.on('data')
|
|
* - child.stderr.on('data')
|
|
* - child.on('close')
|
|
* - child.kill()
|
|
*/
|
|
spawn() {
|
|
const client = this.client;
|
|
|
|
//client.debug("ZfsProcessManager spawn", this);
|
|
|
|
// Create an eventEmitter object
|
|
var stdout = new events.EventEmitter();
|
|
var stderr = new events.EventEmitter();
|
|
var proxy = new events.EventEmitter();
|
|
|
|
proxy.stdout = stdout;
|
|
proxy.stderr = stderr;
|
|
proxy.kill = function (signal = "SIGTERM") {
|
|
proxy.emit("kill", signal);
|
|
};
|
|
|
|
const command = this.buildCommand(arguments[0], arguments[1]);
|
|
|
|
client.debug("ZfsProcessManager arguments: " + JSON.stringify(arguments));
|
|
client.logger.verbose("ZfsProcessManager command: " + command);
|
|
|
|
client.exec(command, {}, proxy).catch((err) => {
|
|
proxy.stderr.emit("data", err.message);
|
|
proxy.emit("close", 1, "SIGQUIT");
|
|
});
|
|
|
|
return proxy;
|
|
}
|
|
}
|
|
exports.ZfsSshProcessManager = ZfsSshProcessManager;
|