LCOV - code coverage report
Current view: top level - pkg/kdump - kdump-client.js Coverage Total Hit
Test: cockpit Lines: 68.1 % 113 77
Test Date: 2026-07-17 07:33:01

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2016 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6              : import cockpit from 'cockpit';
       7              : import { proxy as serviceProxy } from 'service';
       8              : import { ConfigFile } from './config-client.js';
       9              : import { ConfigFileSUSE } from './config-client-suse.js';
      10              : 
      11              : import crashKernelScript from './crashkernel.sh';
      12              : import testWritableScript from './testwritable.sh';
      13            3 : const _ = cockpit.gettext;
      14              : 
      15              : /*  initializes the kdump status
      16              :  *  emits "kdumpStatusChanged" when the status changes, along with a status object:
      17              :  *  {
      18              :  *      installed: true/false,
      19              :  *      enabled:   true/false,
      20              :  *      state:     current state,
      21              :  *      config:    settings from kdump.conf
      22              :  *      target:    dump target info, content depends on dump type
      23              :  *                 always contains the keys:
      24              :  *                     type          value in ["local", "nfs", "ssh", "raw", "mount", "unknown"]
      25              :  *                     multipleTargets true if the config file has more than one target defined, false otherwise
      26              :  *  }
      27              :  *
      28              :  */
      29            3 : export class KdumpClient {
      30            3 :     constructor() {
      31            3 :         this.state = {
      32            3 :             installed: undefined,
      33            3 :             enabled: undefined,
      34            3 :             state: undefined,
      35            3 :             config: undefined,
      36            3 :             target: undefined,
      37            3 :         };
      38            3 :         cockpit.event_target(this);
      39              : 
      40              :         // listen to service status changes
      41            3 :         this.kdumpService = serviceProxy("kdump");
      42            3 :         this.kdumpService.addEventListener('changed', () => {
      43            3 :             this.state.installed = this.kdumpService.exists;
      44            3 :             this.state.enabled = this.kdumpService.enabled;
      45            3 :             this.state.state = this.kdumpService.state;
      46            3 :             this.dispatchEvent("kdumpStatusChanged", this.state);
      47            3 :         });
      48              : 
      49              :         // watch the config file
      50            3 :         this.configClient = new ConfigFile("/etc/kdump.conf", true);
      51            3 :         this._watchConfigChanges();
      52              : 
      53            3 :         this.configClient.wait().then(() => {
      54              :             // if no configuration found, try SUSE version
      55            1 :             if (this.configClient.settings === null) {
      56            1 :                 this.configClient.close();
      57            1 :                 this.configClient = new ConfigFileSUSE("/etc/sysconfig/kdump", true);
      58            1 :                 this._watchConfigChanges();
      59            1 :             }
      60            3 :         });
      61            3 :     }
      62              : 
      63            3 :     _watchConfigChanges() {
      64              :         // catch config changes
      65            3 :         this.configClient.addEventListener('kdumpConfigChanged', () => {
      66            3 :             this.state.config = this.configClient.settings;
      67            3 :             this.state.target = this.targetFromSettings(this.configClient.settings);
      68            3 :             this.dispatchEvent("kdumpStatusChanged", this.state);
      69            3 :         });
      70            3 :     }
      71              : 
      72            0 :     ensureOn() {
      73              :         // we consider the state to be "on" when it's enabled and running
      74            0 :         return Promise.all([
      75            0 :             this.kdumpService.enable(),
      76            0 :             this.kdumpService.start()
      77            0 :         ]);
      78            0 :     }
      79              : 
      80            0 :     ensureOff() {
      81              :         // we consider the state to be "off" when it's disabled and stopped
      82            0 :         return Promise.all([
      83            0 :             this.kdumpService.stop(),
      84            0 :             this.kdumpService.disable()
      85            0 :         ]);
      86            0 :     }
      87              : 
      88            0 :     crashKernel() {
      89              :         // crash the system kernel
      90            0 :         return cockpit.script(crashKernelScript, [], { superuser: "require" });
      91            0 :     }
      92              : 
      93            2 :     validateSettings(settings) {
      94            2 :         const target = this.targetFromSettings(settings);
      95            2 :         let path;
      96            2 :         if (target && target.path)
      97            2 :             path = target.path;
      98              :         // if path is invalid or we haven't set one, use default
      99            2 :         if (!path)
     100            2 :             path = "/var/crash";
     101              : 
     102            2 :         return new Promise((resolve, reject) => {
     103            2 :             if (target.type === "local") {
     104              :                 // local path, try to see if we can write
     105            2 :                 cockpit.script(testWritableScript, [path], { superuser: "try" })
     106            2 :                         .then(resolve)
     107            0 :                         .catch(() => reject(cockpit.format(_("Directory $0 isn't writable or doesn't exist."), path)));
     108            2 :                 return;
     109            2 :             } else if (target.type === "nfs") {
     110            2 :                 if (!target.server || !target.server.trim())
     111            0 :                     reject(_("nfs server is empty"));
     112              :                 // IPv6 must be enclosed in square brackets
     113            2 :                 if (target.server.trim().match(/^\[.*[^\]]$/))
     114            0 :                     reject(_("nfs server is not valid IPv6"));
     115            2 :                 if (!target.export || !target.export.trim())
     116            0 :                     reject(_("nfs export is empty"));
     117            2 :             } else if (target.type === "ssh") {
     118            2 :                 if (!target.server || !target.server.trim())
     119            0 :                     reject(_("ssh server is empty"));
     120            2 :                 if (target.sshkey && !target.sshkey.match("/.+"))
     121            0 :                     reject(_("ssh key isn't a path"));
     122            2 :             }
     123              : 
     124              :             /* no-op if already rejected  */
     125            2 :             resolve();
     126            2 :         });
     127            2 :     }
     128              : 
     129            2 :     writeSettings(settings) {
     130            2 :         return this.configClient.write(settings)
     131            2 :                 .then(() => {
     132              :                     // after we've written the new config, we have to restart the service to pick up changes or clean up after errors
     133            0 :                     if (this.kdumpService.enabled) {
     134            0 :                         return this.kdumpService.restart()
     135            0 :                                 .catch(error => this.kdumpService.getRunJournal(["--output=cat", "--identifier=kdumpctl"])
     136            0 :                                         .then(journal => {
     137            0 :                                             error.details = journal;
     138            0 :                                             return Promise.reject(error);
     139            0 :                                         }, ex => {
     140            0 :                                             console.warn("Failed to get journal of kdump.service:", ex.toString());
     141            0 :                                             return Promise.reject(error);
     142            0 :                                         })
     143            0 :                                 );
     144            0 :                     } else {
     145            2 :                         return true;
     146            2 :                     }
     147            2 :                 });
     148            2 :     }
     149              : 
     150            0 :     exportConfig(settings) {
     151            0 :         return this.configClient.generateConfig(settings).trim();
     152            0 :     }
     153              : 
     154            3 :     targetFromSettings(settings) {
     155            3 :         const target = {
     156            3 :             type: "unknown",
     157            3 :             multipleTargets: false,
     158            3 :         };
     159              : 
     160            3 :         if (!settings || Object.keys(settings.targets).length === 0)
     161            1 :             return target;
     162              : 
     163              :         // copy first target
     164            3 :         Object.assign(target, Object.values(settings.targets)[0]);
     165            3 :         target.multipleTargets = Object.keys(settings.targets).length > 1;
     166            3 :         return target;
     167            3 :     }
     168            3 : }
        

Generated by: LCOV version 2.0-1