mirror of
https://github.com/cockpit-project/cockpit.git
synced 2026-11-03 21:49:50 -09:00
252 lines
9.2 KiB
JavaScript
252 lines
9.2 KiB
JavaScript
/*
|
|
* Copyright (C) 2022 SUSE LLC
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*/
|
|
|
|
import { ConfigFile } from './config-client.js';
|
|
|
|
/* Parse an dotenv-style config file
|
|
* and monitor it for changes
|
|
*/
|
|
export class ConfigFileSUSE extends ConfigFile {
|
|
/* parse lines of the config file
|
|
* if a line has a valid key=value format, use the key in _internal structure
|
|
* and also store original line, line index, value and optional line suffix / comment
|
|
* if value was quoted it will be stripped of quotes in `value` and `quoted` flag will
|
|
* be used when writing the file to keep original formatting
|
|
* e.g. for line 'someKey="foo" # comment'
|
|
* outputObject._internal["someKey"] = {
|
|
* index: 0,
|
|
* value: "foo",
|
|
* quoted: true,
|
|
* origLine: 'someKey="foo" # comment',
|
|
* suffix: "# comment"
|
|
* }
|
|
* skipNotify: Don't notify about changes, e.g.to avoid multiple updates when writing a file
|
|
*/
|
|
_parseText(rawContent, skipNotify = false) {
|
|
this._dataAvailableResolve();
|
|
|
|
// clear settings if file is empty/missing
|
|
if (!rawContent) {
|
|
this._originalSettings = null;
|
|
this.settings = null;
|
|
if (!skipNotify)
|
|
this.dispatchEvent("kdumpConfigChanged", this.settings);
|
|
return;
|
|
}
|
|
|
|
// if nothing changed, don't bother parsing the content
|
|
if (rawContent === this._rawContent)
|
|
return;
|
|
|
|
this._rawContent = rawContent;
|
|
|
|
// this is the format expected by the UI
|
|
this.settings = {
|
|
_internal: {},
|
|
targets: {},
|
|
compression: { enabled: false, allowed: true },
|
|
nfs_supports_directory: false,
|
|
};
|
|
|
|
this._lines = rawContent.split(/\r?\n/);
|
|
this._lines.forEach((line, index) => {
|
|
const trimmed = line.trim();
|
|
// if the line is empty or only a comment, skip
|
|
if (trimmed.indexOf("#") === 0 || trimmed.length === 0)
|
|
return;
|
|
|
|
// parse KEY=value or KEY="value" line
|
|
let parts = trimmed.match(/^([A-Z_]+)\s*=\s*(.*)$/);
|
|
if (parts === null) {
|
|
console.warn("Malformed kdump config line:", trimmed, "in", this.filename);
|
|
return;
|
|
}
|
|
const key = parts[1];
|
|
let value = parts[2];
|
|
|
|
// value might be quoted
|
|
let quoted = false;
|
|
if (value.startsWith('"')) {
|
|
quoted = true;
|
|
parts = value.match(/^"([^"]*)"\s*(.*)$/);
|
|
// malformed line, no ending quote?
|
|
if (parts === null) {
|
|
console.warn("Incorrectly quoted value in kdump config line:", line, "in", this.filename);
|
|
return;
|
|
}
|
|
} else {
|
|
// not quoted should be simple value but grab everything and quote on write
|
|
parts = value.match(/^([^#]+?)\s*(#.*)?$/);
|
|
if (parts === null)
|
|
parts = ["", ""];
|
|
}
|
|
value = parts[1];
|
|
const suffix = (parts[2] || "").trim();
|
|
|
|
this.settings._internal[key] = {
|
|
index,
|
|
value,
|
|
origLine: line,
|
|
quoted,
|
|
suffix
|
|
};
|
|
});
|
|
|
|
// make sure we copy the original keys so we overwrite the correct lines when saving
|
|
this._originalSettings = { };
|
|
Object.keys(this.settings._internal).forEach((key) => {
|
|
this._originalSettings[key] = { ...this.settings._internal[key] };
|
|
});
|
|
|
|
this._extractSettings();
|
|
|
|
if (!skipNotify)
|
|
this.dispatchEvent("kdumpConfigChanged", this.settings);
|
|
}
|
|
|
|
/* extract settings managed by cockpit from _internal into platform independent model
|
|
*/
|
|
_extractSettings() {
|
|
// generate target(s) from KDUMP_SAVEDIR
|
|
if ("KDUMP_SAVEDIR" in this.settings._internal && this.settings._internal.KDUMP_SAVEDIR.value) {
|
|
let savedir = this.settings._internal.KDUMP_SAVEDIR.value;
|
|
// handle legacy "file" without prefix
|
|
if (savedir.startsWith("/"))
|
|
savedir = "file://" + savedir;
|
|
// server includes "username:password@" and can be empty for file://
|
|
const parts = savedir.match(/^(.*):\/\/([^/]*)(\/.*)$/);
|
|
// malformed KDUMP_SAVEDIR
|
|
if (parts === null) {
|
|
console.warn("Malformed KDUMP_SAVEDIR entry:", savedir, "in", this.filename);
|
|
return;
|
|
}
|
|
const [, scheme, server, path] = parts;
|
|
if (scheme === "file") {
|
|
this.settings.targets.local = {
|
|
type: "local",
|
|
path,
|
|
};
|
|
} else if (scheme === "nfs") {
|
|
this.settings.targets.nfs = {
|
|
type: scheme,
|
|
// on read full path is used as export
|
|
export: path,
|
|
server,
|
|
};
|
|
} else {
|
|
this.settings.targets[scheme] = {
|
|
type: scheme,
|
|
path,
|
|
server,
|
|
};
|
|
// sshkey is used by ssh and sftp/scp
|
|
if ("KDUMP_SSH_IDENTITY" in this.settings._internal) {
|
|
this.settings.targets[scheme].sshkey =
|
|
this.settings._internal.KDUMP_SSH_IDENTITY.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// default to local if no target configured
|
|
if (Object.keys(this.settings.targets).length === 0)
|
|
this.settings.targets.local = { type: "local" };
|
|
|
|
this.settings.compression.enabled = (
|
|
!("KDUMP_DUMPFORMAT" in this.settings._internal) ||
|
|
// TODO: what about other compression formats (lzo, snappy)?
|
|
this.settings._internal.KDUMP_DUMPFORMAT.value === "compressed"
|
|
);
|
|
}
|
|
|
|
/* update single _internal setting to given value
|
|
* make sure setting exists if value is not empty
|
|
* don't delete existing settings
|
|
*/
|
|
_updateSetting(settings, key, value) {
|
|
if (key in settings._internal) {
|
|
settings._internal[key].value = value;
|
|
} else {
|
|
if (value)
|
|
settings._internal[key] = { value };
|
|
}
|
|
}
|
|
|
|
/* transform settings from model back to _internal format
|
|
* this.settings = current state from file
|
|
* settings = in-memory state from UI
|
|
*/
|
|
_persistSettings(settings) {
|
|
// target
|
|
if (Object.keys(settings.targets).length > 0) {
|
|
const target = Object.values(settings.targets)[0];
|
|
|
|
if ("sshkey" in target)
|
|
this._updateSetting(settings, "KDUMP_SSH_IDENTITY", target.sshkey);
|
|
|
|
let savedir;
|
|
// default for empty path (except nfs, see below)
|
|
let path = target.path || "/var/crash";
|
|
if (path && !path.startsWith("/"))
|
|
path = "/" + path;
|
|
if (target.type === "local") {
|
|
savedir = "file://" + path;
|
|
} else if (target.type === "nfs") {
|
|
// override empty path default as nfs path is merged into export on read
|
|
if (!target.path)
|
|
path = "";
|
|
let exprt = target.export;
|
|
if (!exprt.startsWith("/"))
|
|
exprt = "/" + exprt;
|
|
savedir = "nfs://" + target.server + exprt + path;
|
|
} else {
|
|
savedir = target.type + "://" + target.server + path;
|
|
}
|
|
this._updateSetting(settings, "KDUMP_SAVEDIR", savedir);
|
|
}
|
|
// compression
|
|
if (this.settings.compression.enabled !== settings.compression.enabled) {
|
|
if (settings.compression.enabled) {
|
|
this._updateSetting(settings, "KDUMP_DUMPFORMAT", "compressed");
|
|
} else {
|
|
this._updateSetting(settings, "KDUMP_DUMPFORMAT", "ELF");
|
|
}
|
|
}
|
|
return settings;
|
|
}
|
|
|
|
/* generate the config file from raw text and settings
|
|
*/
|
|
generateConfig(settings) {
|
|
settings = this._persistSettings(settings);
|
|
|
|
const lines = this._lines.slice(0);
|
|
|
|
// we take the lines from our last read operation and modify them with the new settings
|
|
Object.keys(settings._internal).forEach((key) => {
|
|
const entry = settings._internal[key];
|
|
|
|
let value = entry.value !== undefined ? entry.value : "";
|
|
// quote what was quoted before + empty values + multi-word values
|
|
if (entry.quoted || value === "" || value.includes(" "))
|
|
value = '"' + value + '"';
|
|
let line = key + "=" + value;
|
|
if (entry.suffix)
|
|
line = line + " " + entry.suffix;
|
|
// this might be a new entry
|
|
if (!(key in this._originalSettings)) {
|
|
lines.push(line);
|
|
return;
|
|
}
|
|
// otherwise edit the old line
|
|
const origEntry = this._originalSettings[key];
|
|
lines[origEntry.index] = line;
|
|
});
|
|
|
|
// make sure file ends with a newline
|
|
if (lines[lines.length - 1] !== "")
|
|
lines.push("");
|
|
return lines.join("\n");
|
|
}
|
|
}
|