diff --git a/pkg/packagekit/updates.jsx b/pkg/packagekit/updates.jsx index ce4b3c4cc6d1..b423ee4c09bd 100644 --- a/pkg/packagekit/updates.jsx +++ b/pkg/packagekit/updates.jsx @@ -21,6 +21,7 @@ import 'polyfills'; // once per application import 'cockpit-dark-theme'; // once per page import cockpit from "cockpit"; +import { fsinfo } from 'cockpit/fsinfo'; import React from "react"; import { createRoot } from 'react-dom/client'; @@ -1079,12 +1080,19 @@ class OsUpdates extends React.Component { debug("tracer parsed restartPackages:", JSON.stringify(restartPackages)); this.setState({ checkRestartAvailable: true, checkRestartRunning: false, restartPackages }); }) - .catch((exception, data) => { + .catch(async (exception, data) => { // tracer not installed or supported (like on Arch)? then fall back to dnf needs-restarting if (exception.message?.includes("ModuleNotFoundError") || exception.message?.includes("UnsupportedDistribution")) { - debug('tracer not installed:', JSON.stringify(exception), "trying dnf needs-restarting"); - return this.checkDnfNeedsRestarting(); + try { + // if there's a history for zypper, we can assume the system uses it + await fsinfo("/var/log/zypp/history", [], { superuser: "require" }); + debug('tracer not installed:', JSON.stringify(exception), "trying zypper ps"); + return this.checkZypperNeedsRestarting(); + } catch { + debug('tracer not installed:', JSON.stringify(exception), "trying dnf needs-restarting"); + return this.checkDnfNeedsRestarting(); + } } // log the error except for some common cases: polkit does not allow it @@ -1106,6 +1114,80 @@ class OsUpdates extends React.Component { }); } + checkZypperNeedsRestarting() { + const restartPackages = { reboot: [], daemons: [], manual: [] }; + return cockpit.spawn(["zypper", "ps", "-ss", "--print", "%s"], { err: "message", superuser: "require" }) + .then((serviceOut) => { + debug("zypper ps -ss succeeded:", serviceOut); + + // set all the services to be manually restarted since it's + // not always clear if it's safe to restart them via cockpit + const data = serviceOut.trim(); + if (data.length !== 0) { + serviceOut.trim() + .split("\n") + .forEach(line => restartPackages.manual.push(line)); + } + + // Check if any kernels are updated since system boot, + // ignoring kernel-firmware updates as they can make things noisy + // + // /var/log/zypper.log can be quite big so it's better to + // handle the processing on machine instead of fetching the data + const kScript = ` + stat -c %z /proc/ | \\ + cut -d. -f 1 | \\ + xargs -i \\ + awk -F'|' -v boot="{}" \\ + '/install\\|kernel/{if (boot <= $1 && index($0, "firmware") == 0) {print $3"-"$4"."$5}}' \\ + /var/log/zypp/history + `; + + cockpit.script(kScript, undefined, { err: "message", superuser: "require" }) + .then(kernels => { + debug("zypper kernel scripts succeeded:", kernels); + + if (kernels.trim().length == 0) { + return; + } + + kernels.trim() + .split("\n") + .forEach(line => { restartPackages.reboot.push(line.trim()) }); + }) + .catch(ex => { + if (ex.problem !== "not-found" && + // polkit does not allow it + ex.problem !== "access-denied" && + // or unprivileged session + ex.problem !== "authentication-failed" && + // or the session goes away while checking + ex.problem !== "terminated") + console.error("zypper kernel fetching failed:", ex.toString()); + }) + .then(() => { + let checkRestartAvailable = false; + if (restartPackages.reboot.length !== 0 || restartPackages.manual.length !== 0) + checkRestartAvailable = true; + + this.setState({ checkRestartAvailable, checkRestartRunning: false, restartPackages }); + }); + }).catch((ex) => { + // log the error except for some common cases: no zypper + if (ex.problem !== "not-found" && + // polkit does not allow it + ex.problem !== "access-denied" && + // or unprivileged session + ex.problem !== "authentication-failed" && + // or the session goes away while checking + ex.problem !== "terminated") + console.error("zypper ps -ss failed:", ex); + + // act like it's not available (demand reboot after every update) + this.setState({ checkRestartAvailable: false, checkRestartRunning: false, restartPackages }); + }); + } + checkDnfNeedsRestarting() { const restartPackages = { reboot: [], daemons: [], manual: [] };