* Copyright (C) 2016 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import cockpit from 'cockpit';
import { proxy as serviceProxy } from 'service';
import { ConfigFile } from './config-client.js';
import { ConfigFileSUSE } from './config-client-suse.js';
import crashKernelScript from './crashkernel.sh';
import testWritableScript from './testwritable.sh';
const _ = cockpit.gettext;
* emits "kdumpStatusChanged" when the status changes, along with a status object:
* {
* installed: true/false,
* enabled: true/false,
* state: current state,
* config: settings from kdump.conf
* target: dump target info, content depends on dump type
* always contains the keys:
* type value in ["local", "nfs", "ssh", "raw", "mount", "unknown"]
* multipleTargets true if the config file has more than one target defined, false otherwise
* }
*
*/
export class KdumpClient {
constructor() {
this.state = {
installed: undefined,
enabled: undefined,
state: undefined,
config: undefined,
target: undefined,
};
cockpit.event_target(this);
this.kdumpService = serviceProxy("kdump");
this.kdumpService.addEventListener('changed', () => {
this.state.installed = this.kdumpService.exists;
this.state.enabled = this.kdumpService.enabled;
this.state.state = this.kdumpService.state;
this.dispatchEvent("kdumpStatusChanged", this.state);
});
this.configClient = new ConfigFile("/etc/kdump.conf", true);
this._watchConfigChanges();
this.configClient.wait().then(() => {
if (this.configClient.settings === null) {
this.configClient.close();
this.configClient = new ConfigFileSUSE("/etc/sysconfig/kdump", true);
this._watchConfigChanges();
}
});
}
_watchConfigChanges() {
this.configClient.addEventListener('kdumpConfigChanged', () => {
this.state.config = this.configClient.settings;
this.state.target = this.targetFromSettings(this.configClient.settings);
this.dispatchEvent("kdumpStatusChanged", this.state);
});
}
ensureOn() {
return Promise.all([
this.kdumpService.enable(),
this.kdumpService.start()
]);
}
ensureOff() {
return Promise.all([
this.kdumpService.stop(),
this.kdumpService.disable()
]);
}
crashKernel() {
return cockpit.script(crashKernelScript, [], { superuser: "require" });
}
validateSettings(settings) {
const target = this.targetFromSettings(settings);
let path;
if (target && target.path)
path = target.path;
if (!path)
path = "/var/crash";
return new Promise((resolve, reject) => {
if (target.type === "local") {
cockpit.script(testWritableScript, [path], { superuser: "try" })
.then(resolve)
.catch(() => reject(cockpit.format(_("Directory $0 isn't writable or doesn't exist."), path)));
return;
} else if (target.type === "nfs") {
if (!target.server || !target.server.trim())
reject(_("nfs server is empty"));
if (target.server.trim().match(/^\[.*[^\]]$/))
reject(_("nfs server is not valid IPv6"));
if (!target.export || !target.export.trim())
reject(_("nfs export is empty"));
} else if (target.type === "ssh") {
if (!target.server || !target.server.trim())
reject(_("ssh server is empty"));
if (target.sshkey && !target.sshkey.match("/.+"))
reject(_("ssh key isn't a path"));
}
resolve();
});
}
writeSettings(settings) {
return this.configClient.write(settings)
.then(() => {
if (this.kdumpService.enabled) {
return this.kdumpService.restart()
.catch(error => this.kdumpService.getRunJournal(["--output=cat", "--identifier=kdumpctl"])
.then(journal => {
error.details = journal;
return Promise.reject(error);
}, ex => {
console.warn("Failed to get journal of kdump.service:", ex.toString());
return Promise.reject(error);
})
);
} else {
return true;
}
});
}
exportConfig(settings) {
return this.configClient.generateConfig(settings).trim();
}
targetFromSettings(settings) {
const target = {
type: "unknown",
multipleTargets: false,
};
if (!settings || Object.keys(settings.targets).length === 0)
return target;
Object.assign(target, Object.values(settings.targets)[0]);
target.multipleTargets = Object.keys(settings.targets).length > 1;
return target;
}
}