Files
webmin/package-updates/package-updates-lib.pl
Ilia Ross 9faa2b4323 Fix failed DNF update checks showing no updates
This PR fixes the Package Updates module reporting "no updates" when dnf check-update fails.

DNF prints its error to stderr and exits with status 1, but the module only read stdout, so a failed check looked like an empty list and was cached for hours. Users could go weeks or months without seeing that updates were blocked!

The module now checks the exit status. When a check fails, it keeps the last known list, shows the error on the module and refresh pages, and retries after 15 minutes instead of caching an empty result.

Scheduled checks email the failure instead of acting on the old list, and in new-only mode the failure is emailed once until a check succeeds again.

Fixes https://forum.virtualmin.com/t/no-warning-of-dnf-error-suppressing-update/138052
2026-09-25 02:10:53 +02:00

913 lines
26 KiB
Perl

# Functions for checking for updates to packages from YUM, APT or some other
# update system.
BEGIN { push(@INC, ".."); };
eval "use WebminCore;";
&init_config();
&foreign_require("software", "software-lib.pl");
&foreign_require("cron", "cron-lib.pl");
&foreign_require("webmin", "webmin-lib.pl");
# Known OS package auto-update services that can overlap with Webmin's
# scheduled package updates.
@auto_update_services = (
"unattended-upgrades",
"dnf-automatic.timer",
"dnf-automatic-install.timer",
"dnf-automatic-download.timer",
"dnf-automatic-notifyonly.timer",
);
# Cache files for the package lists and the error from a failed check for
# updates
$available_cache_file = &cache_file_path("available.cache");
$current_cache_file = &cache_file_path("current.cache");
$updates_cache_file = &cache_file_path("updates.cache");
$held_updates_cache_file = &cache_file_path("held-updates.cache");
$updates_error_file = &cache_file_path("updates.error");
$failed_check_retry_mins = 15; # Minutes before a failed check is retried
$cron_cmd = "$module_config_directory/update.pl";
$yum_cache_file = &cache_file_path("yumcache");
$apt_cache_file = &cache_file_path("aptcache");
$yum_changelog_cache_dir = &cache_file_path("yumchangelog");
$update_progress_dir = "$module_var_directory/progress";
$notified_file = "$module_var_directory/notified";
# list_enabled_auto_update_services()
# Returns known OS-level auto-update services that are currently enabled.
sub list_enabled_auto_update_services
{
return ( ) if (!&foreign_check("init"));
&foreign_require("init");
my @rv;
foreach my $service (@auto_update_services) {
next if (&init::action_status($service) != 2); # not enabled
my $service_name = $service;
$service_name =~ s/\.[^\.]+$//; # nice name
push(@rv, { 'service' => $service, 'name' => $service_name });
}
return @rv;
}
# disable_enabled_auto_update_services()
# Stops, disables and masks known auto-update services that are enabled.
sub disable_enabled_auto_update_services
{
return ( ) if (!&foreign_check("init"));
&foreign_require("init");
my @services = map { $_->{'service'} } &list_enabled_auto_update_services();
my @rv;
foreach my $service (@services) {
push(@rv, $service) if (&init::deactivate_action($service, 0));
}
return @rv;
}
# cache_file_path(name)
# Returns a path in the /var directory unless the file already exists under
# /etc/webmin
sub cache_file_path
{
my ($name) = @_;
if (-e "$module_config_directory/$name") {
return "$module_config_directory/$name";
}
return "$module_var_directory/$name";
}
# get_software_packages()
# Fills in software::packages with list of installed packages (if missing),
# returns count.
sub get_software_packages
{
if (!$get_software_packages_cache) {
%software::packages = ( );
if (!defined(&software::list_packages)) {
return 0;
}
$get_software_packages_cache = &software::list_packages();
}
return $get_software_packages_cache;
}
# list_current([nocache])
# Returns a list of packages and versions for installed software. Keys are :
# name - The my package name (ie. CSWapache2)
# update - Name used to refer to it by the updates system (ie. apache2)
# version - Version number
# epoch - Epoch part of the version
# desc - Human-readable description
sub list_current
{
my ($nocache) = @_;
if ($nocache || &cache_expired($current_cache_file)) {
my $n = &get_software_packages();
my @rv;
for(my $i=0; $i<$n; $i++) {
push(@rv, { 'name' => $software::packages{$i,'name'},
'update' => $software::packages{$i,'name'},
'version' =>
$software::packages{$i,'version'},
'epoch' =>
$software::packages{$i,'epoch'},
'desc' =>
$software::packages{$i,'desc'},
'system' => $software::update_system,
});
&fix_pkgadd_version($rv[$#rv]);
}
# Filter out dupes and sort by name
@rv = &filter_duplicates(\@rv);
&write_cache_file($current_cache_file, \@rv);
return @rv;
}
else {
return &read_cache_file($current_cache_file);
}
}
# list_available(nocache)
# Returns the names and versions of packages available from the update system
sub list_available
{
my ($nocache) = @_;
my $expired = &cache_expired($available_cache_file);
if ($nocache || $expired == 2 ||
$expired == 1 && !&check_available_lock()) {
# Get from update system
my @rv;
my @avail = &packages_available();
foreach my $avail (@avail) {
$avail->{'update'} = $avail->{'name'};
$avail->{'name'} = &csw_to_pkgadd($avail->{'name'});
push(@rv, $avail);
}
# Filter out dupes and sort by name
@rv = &filter_duplicates(\@rv);
if (!@rv) {
# Failed .. fall back to cache
@rv = &read_cache_file($available_cache_file);
}
&write_cache_file($available_cache_file, \@rv);
return @rv;
}
else {
return &read_cache_file($available_cache_file);
}
}
# check_available_lock()
# Returns 1 if the package update system is currently locked
sub check_available_lock
{
if ($software::update_system eq "yum") {
return &check_pid_file("/var/run/yum.pid");
}
return 0;
}
# filter_duplicates(&packages)
# Given a list of package structures, orders them by name and version number,
# and removes dupes with the same name
sub filter_duplicates
{
my ($pkgs) = @_;
my @rv = sort { $a->{'name'} cmp $b->{'name'} ||
&compare_versions($b, $a) } @$pkgs;
my %done;
return grep { !$done{$_->{'name'},$_->{'system'}}++ } @rv;
}
# cache_expired(file)
# Checks if some cache has expired. Returns 0 if OK, 1 if expired, 2 if
# totally missing.
sub cache_expired
{
my ($file) = @_;
my @st = stat($file);
return 2 if (!@st);
if (!$config{'cache_time'} || time()-$st[9] > $config{'cache_time'}*60*60) {
return 1;
}
return 0;
}
sub write_cache_file
{
my ($file, $arr) = @_;
eval "use Data::Dumper";
if (!$@) {
&open_tempfile(FILE, ">$file");
&print_tempfile(FILE, Dumper($arr));
&close_tempfile(FILE);
$read_cache_file_cache{$file} = $arr;
}
}
# read_cache_file(file)
# Returns the contents of some cache file, as an array ref
sub read_cache_file
{
my ($file) = @_;
if (defined($read_cache_file_cache{$file})) {
return @{$read_cache_file_cache{$file}};
}
if (-r $file) {
do $file;
$read_cache_file_cache{$file} = $VAR1;
return @$VAR1;
}
return ( );
}
# save_updates_error([error])
# Saves the error from a failed check for updates, or removes the saved
# error if none is given
sub save_updates_error
{
my ($err) = @_;
if ($err) {
# Save it for the module pages and the scheduled job to report
&open_tempfile(ERR, ">$updates_error_file");
&print_tempfile(ERR, $err);
&close_tempfile(ERR);
}
else {
# No error, so stop warning about an earlier failure
unlink($updates_error_file);
}
}
# get_updates_error()
# Returns the error if the last check for updates failed, or undef
sub get_updates_error
{
return undef if (!-r $updates_error_file);
return &read_file_contents($updates_error_file);
}
# compare_versions(&pkg1, &pkg2)
# Returns -1 if the version of pkg1 is older than pkg2, 1 if newer, 0 if same.
sub compare_versions
{
my ($pkg1, $pkg2) = @_;
if ($pkg1->{'system'} eq 'webmin' && $pkg2->{'system'} eq 'webmin') {
# Webmin module version compares are always numeric
return $pkg1->{'version'} <=> $pkg2->{'version'};
}
my $ec = $pkg1->{'epoch'} <=> $pkg2->{'epoch'};
if ($ec && ($pkg1->{'epoch'} eq '' || $pkg2->{'epoch'} eq '') &&
$pkg1->{'system'} eq 'apt') {
# On some Debian systems, we don't have any epoch
$ec = undef;
}
return $ec ||
&software::compare_versions($pkg1->{'version'}, $pkg2->{'version'});
}
sub find_cron_job
{
my @jobs = &cron::list_cron_jobs();
my ($job) = grep { $_->{'user'} eq 'root' &&
$_->{'command'} eq $cron_cmd } @jobs;
return $job;
}
# packages_available()
# Returns a list of all available packages, as hash references with name and
# version keys. These come from the APT, YUM or CSW update system, if available.
# If not, nothing is returned.
sub packages_available
{
if (@packages_available_cache) {
return @packages_available_cache;
}
if (defined(&software::update_system_available)) {
# From a decent package system
my @rv = software::update_system_available();
my %done;
foreach my $p (@rv) {
$p->{'system'} = $software::update_system;
$p->{'version'} =~ s/,REV=.*//i; # For CSW
if ($p->{'system'} eq 'apt' && !$p->{'source'}) {
$p->{'source'} =
$p->{'file'} =~ /virtualmin/i ? 'virtualmin' :
$p->{'file'} =~ /debian/i ? 'debian' :
$p->{'file'} =~ /ubuntu/i ? 'ubuntu' : undef;
}
$done{$p->{'name'}} = $p;
}
if ($software::update_system eq "yum" &&
&has_command("up2date")) {
# YUM is the package system select, but up2date is installed
# too (ie. RHEL). Fetch its packages too..
if (!$done_rhn_lib++) {
do "../software/rhn-lib.pl";
}
my @rhnrv = &update_system_available();
foreach my $p (@rhnrv) {
$p->{'system'} = "rhn";
my $d = $done{$p->{'name'}};
if ($d) {
# Seen already .. but is this better?
if (&compare_versions($p, $d) > 0) {
# Yes .. replace
@rv = grep { $_ ne $d } @rv;
push(@rv, $p);
$done{$p->{'name'}} = $p;
}
}
else {
push(@rv, $p);
$done{$p->{'name'}} = $p;
}
}
}
@packages_available_cache = @rv;
return @rv;
}
return ( );
}
sub supports_updates_available
{
return defined(&software::update_system_updates);
}
# supports_package_holds()
# Returns true if the current update system can list and change package holds.
sub supports_package_holds
{
return 0 if (!defined(&software::list_update_system_holds) ||
!defined(&software::update_system_hold) ||
!defined(&software::update_system_hold_flags));
return &software::supports_update_system_holds()
if (defined(&software::supports_update_system_holds));
return 1;
}
# list_package_holds()
# Returns the package names currently held by the update system.
sub list_package_holds
{
return ( ) if (!&supports_package_holds());
return &software::list_update_system_holds();
}
# package_is_held(package, [holds])
# Returns true if a package is in a supplied or freshly-read list of holds.
sub package_is_held
{
my ($name, $holds) = @_;
my @holds = $holds ? @$holds : &list_package_holds();
return 1 if (grep { $_ eq $name } @holds);
if ($software::update_system eq 'apt' &&
defined(&software::strip_apt_package_arch)) {
my $base = &software::strip_apt_package_arch($name);
return 1 if (grep {
&software::strip_apt_package_arch($_) eq $base
} @holds);
}
return 0;
}
# update_package_holds(&packages, hold)
# Holds or unholds packages. Returns undef on success, or an error message.
sub update_package_holds
{
my ($packages, $hold) = @_;
return $text{'hold_enotsupported'} if (!&supports_package_holds());
return &software::update_system_hold($packages, $hold);
}
# updates_available([no-cache], [include-held])
# Returns an array of hash refs of package updates available, according to
# the update system, with caching. If the check fails, returns the last
# known list and saves the error for get_updates_error.
sub updates_available
{
my ($nocache, $include_held) = @_;
# Held updates have their own cache file and in-memory list
my $cache_file = $include_held ? $held_updates_cache_file :
$updates_cache_file;
my $cache = $include_held ? \@held_updates_available_cache :
\@updates_available_cache;
# Load the list, unless a non-empty one is already in memory
if (!scalar(@$cache)) {
if ($nocache || &cache_expired($cache_file)) {
# Get from original source
@$cache = &software::update_system_updates($include_held);
if ($software::update_system_error) {
# The check failed, so keep the last known list rather
# than caching an empty one, and save the error
@$cache = &read_cache_file($cache_file);
&save_updates_error($software::update_system_error);
}
else {
# The check worked, so clear any earlier error
&save_updates_error(undef);
}
# Add the fields callers use, and cache the list
foreach my $a (@$cache) {
$a->{'update'} = $a->{'name'};
$a->{'system'} = $software::update_system;
}
&write_cache_file($cache_file, $cache);
if ($software::update_system_error) {
# Expire the cache after $failed_check_retry_mins
# minutes, so a failed check is retried soon but not on
# every page load. A shorter cache time is left alone.
my $retry = time() - $config{'cache_time'}*60*60 +
$failed_check_retry_mins*60;
utime($retry, $retry, $cache_file) if ($retry < time());
}
}
else {
# Use on-disk cache
@$cache = &read_cache_file($cache_file);
}
}
return @$cache;
}
# package_install(package-name, [system], [new-install], [flags])
# Install some package, either from an update system or from Webmin. Returns
# a list of updated package names.
sub package_install
{
my ($name, $system, $install, $flags) = @_;
$system ||= $software::update_system;
my @rv;
my $pkg;
my $include_held = $system eq $software::update_system &&
&supports_package_holds() && defined($flags) &&
$flags eq &software::update_system_hold_flags();
# First get from list of updates
($pkg) = grep { $_->{'update'} eq $name &&
($_->{'system'} eq $system || !$system) }
sort { &compare_versions($b, $a) }
&list_possible_updates(0, 0, $include_held);
if (!$pkg) {
# Then try list of all available packages
($pkg) = grep { $_->{'update'} eq $name &&
($_->{'system'} eq $system || !$system) }
sort { &compare_versions($b, $a) }
&list_available(0);
}
if (!$pkg && $install) {
# Assume that it will exist
$pkg = { 'system' => $system || $software::update_system,
'name' => $name };
}
if (!$pkg) {
print &text('update_efindpkg', $name),"<p>\n";
return ( );
}
if (defined(&software::update_system_install)) {
# Using some update system, like YUM or APT
&clean_environment();
if ($software::update_system eq $pkg->{'system'}) {
# Can use the default system
if ($name eq "apache2" &&
$pkg->{'system'} eq 'apt') {
# If updating the apache2 package on an apt system
# and apache2-mpm-prefork is installed, also update it
# so that ubuntu doesn't pull in the apache2-mpm-worker
# instead, which breaks PHP :-(
local @pinfo = &software::package_info(
"apache2-mpm-prefork");
if (@pinfo) {
$name .= " apache2-mpm-prefork";
}
}
@rv = &software::update_system_install($name, undef, 1, $flags);
}
else {
# Another update system exists!! Use it..
if (!$done_rhn_lib++) {
do "../software/$pkg->{'system'}-lib.pl";
}
if (!$done_rhn_text++) {
%text = ( %text, %software::text );
}
@rv = &update_system_install($name, undef, 1, $flags);
}
&reset_environment();
}
else {
&error("Don't know how to install package $pkg->{'name'} with type $pkg->{'type'}");
}
# Flush installed cache
unlink($current_cache_file);
return @rv;
}
# package_install_multiple(&package-names, system, [new-install], [flags])
# Install multiple packages, either from an update system or from Webmin.
# Returns a list of updated package names.
sub package_install_multiple
{
my ($names, $system, $install, $flags) = @_;
$system ||= $software::update_system;
my @rv;
my $pkg;
if (defined(&software::update_system_install)) {
# Using some update system, like YUM or APT
&clean_environment();
if ($software::update_system eq $system) {
# Can use the default system
@rv = &software::update_system_install(
join(" ", @$names), undef, 1, $flags);
}
else {
# Another update system exists!! Use it..
if (!$done_rhn_lib++) {
do "../software/$pkg->{'system'}-lib.pl";
}
if (!$done_rhn_text++) {
%text = ( %text, %software::text );
}
@rv = &update_system_install(join(" ", @$names), undef, 1, $flags);
}
&reset_environment();
}
else {
&error("Don't know how to install packages");
}
# Flush installed cache
unlink($current_cache_file);
return @rv;
}
# list_package_operations(package|packages, system, [flags])
# Returns packages and dependencies that would be installed or updated.
sub list_package_operations
{
my ($name, $system, $flags) = @_;
if (defined(&software::update_system_operations)) {
my @rv = &software::update_system_operations($name, $flags);
foreach my $p (@rv) {
$p->{'system'} = $system;
}
return @rv;
}
return ( );
}
# list_possible_updates([nocache], [nocache-no-data], [include-held])
# Returns a list of updates that are available. Each element in the array
# is a hash ref containing a name, version, description and severity flag.
# Intended for calling from themes. Nocache 0=cache everything, 1=flush all
# caches, 2=flush only current. Nocache-no-data prohibits collecting data
sub list_possible_updates
{
my ($nocache, $nocache_no_data, $include_held) = @_;
my @rv;
return @rv if ($nocache_no_data);
my @current = &list_current($nocache);
if (&supports_updates_available()) {
# Software module supplies a function that can list just packages
# that need updating
my %currentmap;
foreach my $c (@current) {
$currentmap{$c->{'name'},$c->{'system'}} ||= $c;
}
foreach my $a (&updates_available($nocache == 1, $include_held)) {
my $c = $currentmap{$a->{'name'},$a->{'system'}};
next if (!$c);
next if ($a->{'held'} && !$include_held);
next if ($a->{'version'} eq $c->{'version'} &&
$a->{'epoch'} eq $c->{'epoch'});
push(@rv, { 'name' => $a->{'name'},
'update' => $a->{'update'},
'system' => $a->{'system'},
'version' => $a->{'version'},
'oldversion' => $c->{'version'},
'epoch' => $a->{'epoch'},
'oldepoch' => $c->{'epoch'},
'security' => $a->{'security'},
'held' => $a->{'held'},
'source' => $a->{'source'},
'desc' => $c->{'desc'} || $a->{'desc'} });
}
}
else {
# Compute from current and available list
my @avail = &list_available($nocache == 1);
my %availmap;
foreach my $a (@avail) {
my $oa = $availmap{$a->{'name'},$a->{'system'}};
if (!$oa || &compare_versions($a, $oa) > 0) {
$availmap{$a->{'name'},$a->{'system'}} = $a;
}
}
foreach my $c (sort { $a->{'name'} cmp $b->{'name'} } @current) {
# Work out the status
my $a = $availmap{$c->{'name'},$c->{'system'}};
if ($a->{'version'} && &compare_versions($a, $c) > 0) {
# A regular update is available
push(@rv, { 'name' => $a->{'name'},
'update' => $a->{'update'},
'system' => $a->{'system'},
'version' => $a->{'version'},
'oldversion' => $c->{'version'},
'epoch' => $a->{'epoch'},
'oldepoch' => $c->{'epoch'},
'security' => $a->{'security'},
'source' => $a->{'source'},
'desc' => $c->{'desc'} || $a->{'desc'},
'severity' => 0 });
}
}
}
@rv = &filter_duplicates(\@rv);
return @rv;
}
# list_possible_installs([nocache])
# Returns a list of packages that could be installed, but are not yet
sub list_possible_installs
{
my ($nocache) = @_;
my @rv;
my @current = &list_current($nocache);
my @avail = &list_available($nocache == 1);
foreach my $a (sort { $a->{'name'} cmp $b->{'name'} } @avail) {
($c) = grep { $_->{'name'} eq $a->{'name'} &&
$_->{'system'} eq $a->{'system'} } @current;
if (!$c && &installation_candiate($a)) {
push(@rv, { 'name' => $a->{'name'},
'update' => $a->{'update'},
'system' => $a->{'system'},
'version' => $a->{'version'},
'epoch' => $a->{'epoch'},
'desc' => $a->{'desc'},
'severity' => 0 });
}
}
return @rv;
}
# csw_to_pkgadd(package)
# On Solaris systems, convert a CSW package name like ap2_modphp5 to a
# real package name like CSWap2modphp5
sub csw_to_pkgadd
{
my ($pn) = @_;
if ($gconfig{'os_type'} eq 'solaris') {
$pn =~ s/[_\-]//g;
$pn = "CSW$pn";
}
return $pn;
}
# fix_pkgadd_version(&package)
# If this is Solaris and the package version is missing, we need to make
# a separate pkginfo call to get it.
sub fix_pkgadd_version
{
my ($pkg) = @_;
if ($gconfig{'os_type'} eq 'solaris') {
if (!$pkg->{'version'}) {
# Make an extra call to get the version
my @pinfo = &software::package_info($pkg->{'name'});
$pinfo[4] =~ s/,REV=.*//i;
$pkg->{'version'} = $pinfo[4];
}
else {
# Trip off the REV=
$pkg->{'version'} =~ s/,REV=.*//i;
}
}
$pkg->{'desc'} =~ s/^\Q$pkg->{'update'}\E\s+\-\s+//;
}
# installation_candiate(&package)
# Returns 1 if some package can be installed, even when it currently isn't.
# Always true for now.
sub installation_candiate
{
my ($p) = @_;
return 1;
}
# clear_repository_cache()
# Clear any YUM or APT caches
sub clear_repository_cache
{
if ($software::update_system eq "yum") {
&execute_command("$software::yum_command clean all");
}
elsif ($software::update_system eq "apt") {
&execute_command("apt-get update");
}
elsif ($software::update_system eq "ports") {
&foreign_require("proc");
foreach my $cmd ("portsnap fetch",
"portsnap update || portsnap extract") {
my ($fh, $pid) = &proc::pty_process_exec($cmd);
while(<$fh>) { }
close($fh);
}
}
}
# split_epoch(version)
# Splits a version formatted like 5:x.yy into an epoch and real version
sub split_epoch
{
my ($ver) = @_;
if ($ver =~ /^(\d+):(\S+)$/) {
return ($1, $2);
}
return (undef, $ver);
}
# get_changelog(&pacakge)
# If possible, returns information about what has changed in some update
sub get_changelog
{
my ($pkg) = @_;
if ($pkg->{'system'} eq 'yum') {
# See if yum supports changelog
if (!defined($supports_yum_changelog)) {
my $out = &backquote_command("$software::yum_command -h 2>&1 </dev/null");
$supports_yum_changelog = $out =~ /changelog|updateinfo/ ? 1 : 0;
}
return undef if (!$supports_yum_changelog);
# Check if we have this info cached
my $cfile = $yum_changelog_cache_dir."/".
$pkg->{'name'}."-".$pkg->{'version'};
my $cl = &read_file_contents($cfile);
if (!$cl && $software::yum_command =~ /yum/) {
# Run yum changelog for this package and version
my $started = 0;
&open_execute_command(YUMCL,
"$software::yum_command changelog all ".
quotemeta($pkg->{'name'}), 1, 1);
while(<YUMCL>) {
s/\r|\n//g;
if (/^\Q$pkg->{'name'}-$pkg->{'version'}\E/) {
$started = 1;
}
elsif (/^==========/ || /^changelog stats/) {
$started = 0;
}
elsif ($started) {
$cl .= $_."\n";
}
}
close(YUMCL);
}
elsif (!$cl && $software::yum_command =~ /dnf/) {
# Run dnf updateinfo for this package and version
&open_execute_command(DNFUI,
"$software::yum_command updateinfo info ".
quotemeta($pkg->{'name'}), 1, 1);
while(<DNFUI>) {
s/\r|\n//g;
if (/^\s*Description\s*:\s*(.*)/) {
$started = 1;
$cl .= $1."\n";
}
elsif ($started && /^\s*:\s*(.*)/) {
$cl .= $1."\n";
}
else {
$started = 0;
}
}
close(DNFUI);
}
if ($cl) {
# Save the cache
if (!-d $yum_changelog_cache_dir) {
&make_dir($yum_changelog_cache_dir, 0700);
}
&open_tempfile(CACHE, ">$cfile");
&print_tempfile(CACHE, $cl);
&close_tempfile(CACHE);
}
return $cl;
}
return undef;
}
# flush_package_caches()
# Clears the package list caches, so the next lookups fetch fresh data. Also
# removes any saved error from a failed check for updates.
sub flush_package_caches
{
unlink($current_cache_file);
unlink($available_cache_file);
# Expire the update lists rather than deleting them, so a failed check can
# still fall back to the last known list
foreach my $f ($updates_cache_file, $held_updates_cache_file) {
utime(0, 0, $f) if (-e $f);
}
# Delete the remaining cache files and the saved error
unlink($available_cache_file.'0');
unlink($available_cache_file.'1');
unlink($updates_error_file);
# Clear the lists held in memory by this process
@packages_available_cache = ( );
@updates_available_cache = ( );
@held_updates_available_cache = ( );
%read_cache_file_cache = ( );
}
# list_for_mode(mode, nocache)
# If not is 'updates' or 'security', return just updates. Otherwise, return
# all available packages.
sub list_for_mode
{
my ($mode, $nocache) = @_;
return grep { $_->{'held'} }
&list_possible_updates($nocache, 0, 1) if ($mode eq 'held');
return $mode eq 'updates' || $mode eq 'security' ?
&list_possible_updates($nocache) : &list_available($nocache);
}
# check_reboot_required()
# Returns 1 if the package system thinks a reboot is needed
sub check_reboot_required
{
if ($gconfig{'os_type'} eq 'debian-linux') {
if (-e "/var/run/reboot-required") {
return 1;
}
if (&has_command("needrestart")) {
my $out = &backquote_command("needrestart -b -k -n 2>&1 </dev/null");
if ($out =~ /NEEDRESTART-KSTA:\s*(?<status>\d)/m) {
if ($+{status} == 3) {
return 1;
}
}
}
}
elsif ($gconfig{'os_type'} eq 'redhat-linux') {
my $needs_restarting_cmd = "needs-restarting";
my $needs_restarting = has_command($needs_restarting_cmd);
if (!$needs_restarting) {
# Fall back to the DNF subcommand when no standalone command exists
my $dnf = has_command("dnf");
$needs_restarting = "$dnf $needs_restarting_cmd" if ($dnf);
}
if ($needs_restarting) {
my $needs_restarting_rs =
&backquote_command("$needs_restarting -r 2>&1 </dev/null");
return 1
if ("$?" != 0 && index($needs_restarting_rs, $needs_restarting_cmd) == -1);
}
}
return 0;
}
# start_update_progress(&packages)
# Record that a bunch of package updates are in progress by this process
sub start_update_progress
{
my ($pkgs) = @_;
if (!-d $update_progress_dir) {
&make_dir($update_progress_dir, 0700);
}
my $f = "$update_progress_dir/$$";
&write_file($f, { 'pid' => $$,
'pkgs' => join(' ', @$pkgs) });
}
# end_update_progress()
# Clear update progress marker file
sub end_update_progress
{
my $f = "$update_progress_dir/$$";
&unlink_file($f);
}
# get_update_progress()
# Returns a list of hash refs, one per update in progress
sub get_update_progress
{
my @rv;
foreach my $f (glob("$update_progress_dir/*")) {
my %u;
&read_file($f, \%u) || next;
$u{'pid'} || next;
kill(0, $u{'pid'}) || next;
push(@rv, \%u);
}
return @rv;
}
1;