urpm-package-cleanup: fix removal of the old kernels

It has been broken for a long time, may be longer than ROSA has been
using nrj kernels. The tool relied on the version and release numbers of
the kernel packages to find the proper ones - but these numbers have
been (1, 1) for at least several years now.

The relevant parts of urpm-package-cleanup have been rewritten.

There are a few more notable differences compared to the old
implementation:

1. The number of kernels to keep is now read from
/etc/urpmi/kernels.cfg.
2. --count option is no longer supported.
3. If remove_old_kernels is set to no in /etc/urpmi/kernels.cfg,
urpm-package-cleanup --oldkernels will have no effect. This may be
convenient when running urpm-package-cleanup from scripts, etc.
4. The packages required by any kernel*-latest package will not be
removed, so urpm-package-cleanup may keep more kernels than requested.
This commit is contained in:
Evgenii Shatokhin 2017-08-28 19:07:26 +03:00
parent b9911a0845
commit e502756547
3 changed files with 181 additions and 114 deletions

View file

@ -38,10 +38,8 @@ When listing leaf nodes do not list development packages.
When listing leaf nodes do not list packages with files in bin directories.
.PP
.SH "OLDKERNELS OPTIONS"
.IP "\fB\-\-count <COUNT>\fP"
Number of kernel packages to keep on the system (default 2)
.IP "\fB\-\-keepdevel\fP"
Do not remove kernel-devel packages when removing kernels
Do not remove kernel-devel packages when removing kernels.
.PP
.SH "DUPLICATE PACKAGE OPTIONS"
.IP "\fB\-\-cleandupes\fP"
@ -60,8 +58,8 @@ List missing suggestions of installed packages
\fBurpm-package-cleanup --problems\fP
.IP "List all packages that are not in any configured repository:"
\fBurpm-package-cleanup --orphans\fP
.IP "Remove old kernels keeping 3 and leaving old kernel-devel packages installed:"
\fBurpm-package-cleanup --oldkernels --count=3 --keepdevel\fP
.IP "Remove old kernels while leaving old kernel-devel packages installed:"
\fBurpm-package-cleanup --oldkernels --keepdevel\fP
.PP
.IP "List all leaf packages with no files in a bin directory whose name begins with either 'perl' or 'python':"
\fBurpm-package-cleanup --leaves --exclude-bin --leaf-regex="^(perl)|(python)"\fP
@ -73,6 +71,12 @@ configuration file:
.nf
/etc/urpmi/urpmi.cfg
.fi
.PP
urpm-package-cleanup --oldkernels also reads the options from /etc/urpmi/kernels.cfg:
.IP \fBremove_old_kernels (yes|no)\fP
If remove_old_kernels is set to "no", urpm-package-cleanup --oldkernels has no effect.
.IP \fBkernels_to_keep\fP
How many kernels to keep.
.PP
.SH "EXIT CODES"

View file

@ -704,137 +704,150 @@ msgstr " Всего пакетов с пониженной версией: "
msgid "Creating HTML file."
msgstr "Создание HTML файла."
#: urpm-package-cleanup.py:58 urpm-tools/urpm-package-cleanup.py:58
#: urpm-package-cleanup.py:62 urpm-tools/urpm-package-cleanup.py:62
msgid "Find problems in the rpmdb of system and correct them"
msgstr "Найти проблемы в локальной базе RPM и исправить их"
#: urpm-package-cleanup.py:62 urpm-tools/urpm-package-cleanup.py:62
#: urpm-package-cleanup.py:66 urpm-tools/urpm-package-cleanup.py:66
msgid "Query format to use for output."
msgstr "Формат вывода."
#: urpm-package-cleanup.py:65 urpm-tools/urpm-package-cleanup.py:65
#: urpm-package-cleanup.py:69 urpm-tools/urpm-package-cleanup.py:69
msgid "Use non-interactive mode"
msgstr "Работать в неинтерактивном режиме"
#: urpm-package-cleanup.py:68 urpm-tools/urpm-package-cleanup.py:68
#: urpm-package-cleanup.py:72 urpm-tools/urpm-package-cleanup.py:72
msgid "Orphans Options"
msgstr "Осиротевшие пакеты"
#: urpm-package-cleanup.py:71 urpm-tools/urpm-package-cleanup.py:71
#: urpm-package-cleanup.py:75 urpm-tools/urpm-package-cleanup.py:75
msgid "List installed packages which are not available from currently configured repositories"
msgstr "Перечислить пакеты, недоступные в настроенных на текущий момент репозиториях"
#: urpm-package-cleanup.py:75 urpm-tools/urpm-package-cleanup.py:75
#: urpm-package-cleanup.py:79 urpm-tools/urpm-package-cleanup.py:79
msgid "Use only update media. This means that urpmq will search and resolve dependencies only in media marked as containing updates (e.g. which have been created with \"urpmi.addmedia --update\")."
msgstr "Ипользовать только источники обновлений. Это означает, что urpmq будет искать и разрешать зависимости только используя источники, помеченные как источники обновлений (например, которые были добавлены при помощи \"urpmi.addmedia --update\")"
#: urpm-package-cleanup.py:80 urpm-tools/urpm-package-cleanup.py:80
#: urpm-package-cleanup.py:84 urpm-tools/urpm-package-cleanup.py:84
msgid "Select specific media to be used, instead of defaulting to all available media (or all update media if --update is used). No rpm will be found in other media."
msgstr "Выбрать особые источники вместо того чтобы использовать все доступные по умолчанию источники (или все источники обновлений, если указан флаг --update). В других источниках пакеты искаться не будут."
#: urpm-package-cleanup.py:85 urpm-tools/urpm-package-cleanup.py:85
#: urpm-package-cleanup.py:89 urpm-tools/urpm-package-cleanup.py:89
msgid "Do not use the specified media."
msgstr "Не использовать указанные источники."
#: urpm-package-cleanup.py:87 urpm-tools/urpm-package-cleanup.py:87
#: urpm-package-cleanup.py:91 urpm-tools/urpm-package-cleanup.py:91
msgid "Dependency Problems Options"
msgstr "Проблемы с зависимостями"
#: urpm-package-cleanup.py:90 urpm-tools/urpm-package-cleanup.py:90
#: urpm-package-cleanup.py:94 urpm-tools/urpm-package-cleanup.py:94
msgid "List dependency problems in the local RPM database"
msgstr "Перечислить проблемы с зависимостями в локальной базе RPM"
#: urpm-package-cleanup.py:93 urpm-tools/urpm-package-cleanup.py:93
#: urpm-package-cleanup.py:97 urpm-tools/urpm-package-cleanup.py:97
msgid "List missing suggestions of installed packages"
msgstr "Перечислить список мягких зависимостей установленных пакетов"
#: urpm-package-cleanup.py:96 urpm-tools/urpm-package-cleanup.py:96
#: urpm-package-cleanup.py:100 urpm-tools/urpm-package-cleanup.py:100
msgid "Duplicate Package Options"
msgstr "Дублирующиеся пакеты"
#: urpm-package-cleanup.py:99 urpm-tools/urpm-package-cleanup.py:99
#: urpm-package-cleanup.py:103 urpm-tools/urpm-package-cleanup.py:103
msgid "Scan for duplicates in your rpmdb"
msgstr "Найти дубликаты в локальной базе RPM"
#: urpm-package-cleanup.py:102 urpm-tools/urpm-package-cleanup.py:102
#: urpm-package-cleanup.py:106 urpm-tools/urpm-package-cleanup.py:106
msgid "Scan for duplicates in your rpmdb and remove older "
msgstr "Найти дубликаты в локальной базе RPM и удалить более старые"
#: urpm-package-cleanup.py:105 urpm-tools/urpm-package-cleanup.py:105
#: urpm-package-cleanup.py:109 urpm-tools/urpm-package-cleanup.py:109
msgid "disable rpm scriptlets from running when cleaning duplicates"
msgstr "отключить скриптлеты rpm при очистке дубликатов"
#: urpm-package-cleanup.py:107 urpm-tools/urpm-package-cleanup.py:107
#: urpm-package-cleanup.py:111 urpm-tools/urpm-package-cleanup.py:111
msgid "Leaf Node Options"
msgstr "Листовые узлы"
#: urpm-package-cleanup.py:110 urpm-tools/urpm-package-cleanup.py:110
#: urpm-package-cleanup.py:114 urpm-tools/urpm-package-cleanup.py:114
msgid "List leaf nodes in the local RPM database"
msgstr "Перечислить листовые узлы в локальной базе RPM"
#: urpm-package-cleanup.py:113 urpm-tools/urpm-package-cleanup.py:113
#: urpm-package-cleanup.py:117 urpm-tools/urpm-package-cleanup.py:117
msgid "list all packages leaf nodes that do not match leaf-regex"
msgstr "перечислить все пакеты-листовые узлы, имя которых не подходить под регулярное выражение"
#: urpm-package-cleanup.py:117 urpm-tools/urpm-package-cleanup.py:117
#: urpm-package-cleanup.py:121 urpm-tools/urpm-package-cleanup.py:121
msgid "A package name that matches this regular expression (case insensitively) is a leaf"
msgstr "Считать листовым узлом пакет, имя которого подходит по регулярному выражению (регистронезависимо)."
#: urpm-package-cleanup.py:121 urpm-tools/urpm-package-cleanup.py:121
#: urpm-package-cleanup.py:125 urpm-tools/urpm-package-cleanup.py:125
msgid "do not list development packages as leaf nodes"
msgstr "не считать devel пакеты листовыми узлами"
#: urpm-package-cleanup.py:124 urpm-tools/urpm-package-cleanup.py:124
#: urpm-package-cleanup.py:128 urpm-tools/urpm-package-cleanup.py:128
msgid "do not list packages with files in a bin dirs as leaf nodes"
msgstr "не считать пакеты, имеющие файлы в bin директориях, листовыми узлами"
#: urpm-package-cleanup.py:127 urpm-tools/urpm-package-cleanup.py:127
#: urpm-package-cleanup.py:131 urpm-tools/urpm-package-cleanup.py:131
msgid "Old Kernel Options"
msgstr "Старые ядра"
#: urpm-package-cleanup.py:130 urpm-tools/urpm-package-cleanup.py:130
#: urpm-package-cleanup.py:134 urpm-tools/urpm-package-cleanup.py:134
msgid "Remove old kernel and kernel-devel packages"
msgstr "Удалить старые ядра и их devel пакеты."
#: urpm-package-cleanup.py:133 urpm-tools/urpm-package-cleanup.py:133
msgid "Number of kernel packages to keep on the system (default 2)"
msgstr "Количество пакетов с ядрами, которые надо сохранить в системе (по умолчанию 2)"
#: urpm-package-cleanup.py:137 urpm-tools/urpm-package-cleanup.py:137
msgid "Do not remove kernel-devel packages when removing kernels"
msgstr "Не удалять kernel-devel пакеты при удалении ядер"
#: urpm-package-cleanup.py:306 urpm-tools/urpm-package-cleanup.py:306
#: urpm-package-cleanup.py:177 urpm-tools/urpm-package-cleanup.py:177
#, python-format
msgid "Warning: invalid value of kernels_to_keep (%(keep)d) in %(cfg)s."
msgstr "Предупреждение: недопустимое значение параметра kernels_to_keep (%(keep)d) в %(cfg)s."
#: urpm-package-cleanup.py:180 urpm-tools/urpm-package-cleanup.py:180
#, python-format
msgid "Warning: unable to read %(cfg)s: %(err)s."
msgstr "Предупреждение: не удалось прочитать %(cfg)s: %(err)s."
#: urpm-package-cleanup.py:182 urpm-tools/urpm-package-cleanup.py:182
#, python-format
msgid "Warning: %(cfg)s may contain incorrect data: %(err)s."
msgstr "Предупреждение: в %(cfg)s могут быть некорректные данные: %(err)s."
#: urpm-package-cleanup.py:339 urpm-tools/urpm-package-cleanup.py:339
#, python-format
msgid "Warning: neither single nor multi lib arch: %s "
msgstr "Некорректная архитектура: %s "
#: urpm-package-cleanup.py:417 urpm-tools/urpm-package-cleanup.py:417
#: urpm-package-cleanup.py:391 urpm-tools/urpm-package-cleanup.py:391
#, python-format
msgid "Not removing kernel %(kver)s-%(krel)s because it is the running kernel"
msgstr "Невозможно удалить пакет %(kver)s-%(krel)s, потому что это запущенное ядро"
msgid "%(pkg)s will not be removed: it is required by %(req)s."
msgstr "Пакет %(pkg)s не будет удалён, т.к. от его зависит пакет %(req)s."
#: urpm-package-cleanup.py:447 urpm-tools/urpm-package-cleanup.py:447
#: urpm-package-cleanup.py:456 urpm-tools/urpm-package-cleanup.py:456
#, python-format
msgid "%s will not be removed: it might be a custom-built kernel."
msgstr "Пакет %s не будет удалён: возможно, это специальная сборка ядра."
#: urpm-package-cleanup.py:463 urpm-tools/urpm-package-cleanup.py:463
#, python-format
msgid "%s will not be removed: it contains the running kernel."
msgstr "Пакет %s не будет удалён, т.к. содержит работающее в данный момент ядро."
#: urpm-package-cleanup.py:493 urpm-tools/urpm-package-cleanup.py:493
#, python-format
msgid "Package %(qf)s %(prob)s"
msgstr "Пакет %(qf)s %(prob)s"
#: urpm-package-cleanup.py:450 urpm-tools/urpm-package-cleanup.py:450
#: urpm-package-cleanup.py:496 urpm-tools/urpm-package-cleanup.py:496
msgid "Missing suggests:"
msgstr "Недостающие мягкие зависимости:"
#: urpm-package-cleanup.py:458 urpm-tools/urpm-package-cleanup.py:458
#: urpm-package-cleanup.py:504 urpm-tools/urpm-package-cleanup.py:504
msgid "No Problems Found"
msgstr "Проблем не найдено"
#: urpm-package-cleanup.py:473 urpm-tools/urpm-package-cleanup.py:473
msgid "Error: Cannot remove kernels as a user, must be root"
msgstr "Ошибка: Невозможно удалить ядро, нужны права root."
#: urpm-package-cleanup.py:476 urpm-tools/urpm-package-cleanup.py:476
msgid "Error: should keep at least 1 kernel!"
msgstr "Ошибка: нужно оставить хотя бы одно ядро!"
#: urpm-package-cleanup.py:529 urpm-tools/urpm-package-cleanup.py:529
msgid "Error: Cannot remove packages as a user, must be root"
msgstr "Ошибка: невозможно удалить пакет, нужны права root."

View file

@ -42,6 +42,10 @@ def exactlyOne(l):
return len(filter(None, l)) == 1
KERNEL_CFG = '/etc/urpmi/kernels.cfg'
KERNELS_TO_KEEP_DEFAULT = 3
class PackageCleanup():
NAME = 'urpm-package-cleanup'
VERSION = '0.1'
@ -128,10 +132,6 @@ class PackageCleanup():
kernelgrp.add_argument("--oldkernels", default=False,
dest="kernels",action="store_true",
help=_("Remove old kernel and kernel-devel packages"))
kernelgrp.add_argument("--count",default=2,dest="kernelcount",
action="store",
help=_('Number of kernel packages to keep on the '\
'system (default 2)'))
kernelgrp.add_argument("--keepdevel", default=False, dest="keepdevel",
action="store_true",
help=_('Do not remove kernel-devel packages when '
@ -149,6 +149,39 @@ class PackageCleanup():
else:
subprocess.call(['urpme', pkgName])
def get_kernel_options(self):
"""Returns a tuple (remove_old_kernels, kernels_to_keep)
remove_old_kernels (bool) - whether to remove the old kernels.
kernels_to_keep (int) - how many kernels to keep, ignored if
remove_old_kernels is False.
"""
remove_old_kernels = True
to_keep = KERNELS_TO_KEEP_DEFAULT
try:
with open(KERNEL_CFG, 'r') as cfg:
for line in cfg:
(name, tmp, value) = line.partition('=')
name = name.strip()
value = value.strip().lower()
if not name or not value:
continue
if name == 'remove_old_kernels':
if value in ['false', 'no', 'off', '0']:
remove_old_kernels = False
elif name == 'kernels_to_keep':
to_keep = int(value)
if to_keep < 1:
print _('Warning: invalid value of kernels_to_keep (%(keep)d) in %(cfg)s.') % {'keep': to_keep, 'cfg': KERNEL_CFG}
to_keep = KERNELS_TO_KEEP_DEFAULT
except IOError as e:
print _('Warning: unable to read %(cfg)s: %(err)s.') % {'cfg': KERNEL_CFG, 'err': str(e)}
except ValueError as e:
print _('Warning: %(cfg)s may contain incorrect data: %(err)s.') % {'cfg': KERNEL_CFG, 'err': str(e)}
return remove_old_kernels, to_keep
@staticmethod
def _genDeptup(name, flags, version):
@ -351,82 +384,95 @@ class PackageCleanup():
return True
return False
def _get_kernels(self):
"""return a list of all installed kernels, sorted newest to oldest"""
def _required_by_latest(self, pkg, latest):
"""Checks whether 'pkg' is required by some package from 'latest'."""
for lp in latest:
if pkg['name'] in lp['requires']:
print _('%(pkg)s will not be removed: it is required by %(req)s.') % {'pkg': pkg['name'], 'req': lp['name']}
return True
return False
def _devel_to_remove(self, to_remove, kernels, latest):
"""Returns the list of kernel development packages to remove.
The list includes the devel packages corresponding to the kernels
from 'to_remove' and the packages that have no matching kernel package
in 'kernels'.
The devel packages required by kernel*-latest ('latest' list) will
not be removed.
"""
# Get the list of devel packages for the kernels.
ts = rpm.TransactionSet()
mi = ts.dbMatch('provides','kernel')
kernlist = []
devel = [pkg for pkg in ts.dbMatch('provides','kernel-devel')]
for h in mi:
kernlist.append(h)
to_remove_names = [p['name'] for p in to_remove]
kernel_names = [p['name'] for p in kernels]
kernlist.sort()
kernlist.reverse()
return kernlist
def _get_old_kernel_devel(self, kernels, removelist):
""" List all kernel devel packages that either belong to kernel versions that
are no longer installed or to kernel version that are in the removelist"""
devellist = []
ts = rpm.TransactionSet()
mi = ts.dbMatch('provides','kernel-devel')
for po in mi:
# For all kernel-devel packages see if there is a matching kernel
# in kernels but not in removelist
keep = False
for kernel in kernels:
if kernel in removelist:
devel_to_remove = []
for pkg in devel:
if self._required_by_latest(pkg, latest):
continue
(kname,karch,kepoch,kver,krel) = (kernel['name'],kernel['arch'],kernel['epoch'],kernel['version'],kernel['release'])
(dname,darch,depoch,dver,drel) = (po['name'],po['arch'],po['epoch'],po['version'],po['release'])
if (karch,kepoch,kver,krel) == (darch,depoch,dver,drel):
keep = True
if not keep:
devellist.append(po)
return devellist
kname = pkg['name'].replace('-devel', '', 1)
if kname not in kernel_names:
devel_to_remove.append(pkg)
continue
if kname in to_remove_names:
devel_to_remove.append(pkg)
continue
return devel_to_remove
def _remove_old_kernels(self, count, keepdevel):
"""Remove old kernels, keep at most count kernels (and always keep the running
kernel"""
count = int(count)
kernels = self._get_kernels()
runningkernel = os.uname()[2]
# Vanilla kernels dont have a release, only a version
if '-' in runningkernel:
splt = runningkernel.split('-')
if len(splt) == 2:
(kver,krel) = splt
else: # Handle cases where a custom build kernel has an extra '-' in the release
kver=splt[1]
krel="-".join(splt[1:])
if krel.split('.')[-1] == os.uname()[-1]:
krel = ".".join(krel.split('.')[:-1])
else:
kver = runningkernel
krel = ""
remove = kernels[count:]
ts = rpm.TransactionSet()
toremove = []
# Remove running kernel from remove list
for kernel in remove:
if kernel['version'] == kver and krel.startswith(kernel['release']):
print _("Not removing kernel %(kver)s-%(krel)s because it is the running kernel") % {'kver': kver, 'krel': krel}
else:
toremove.append(kernel)
# Get the list of installed kernel packages and sort it by
# installation timestamp, from the newest to the oldest.
kernels = [pkg for pkg in ts.dbMatch('provides','kernel')]
kernels.sort(key=lambda k: k[rpm.RPMTAG_INSTALLTIME], reverse=True)
# kernel*-latest packages may require some of the installed kernels
# and/or their devel packages. Such packages will not be removed.
mi = ts.dbMatch()
mi.pattern('name', rpm.RPMMIRE_GLOB, 'kernel*-latest*')
latest = [pkg for pkg in mi]
# Note that the version shown by uname -r has a different format
# compared to the name of the kernel package. Example:
# pkg: kernel-desktop-4.12.0-12.1rosa-x86_64
# uname: 4.12.0-desktop-12.1rosa-x86_64
running = os.uname()[2]
to_remove = []
for kpkg in kernels[count : ]:
# To ['kernel-nrj-desktop', '4.9.20', '1rosa', 'x86_64'] or so.
splt = kpkg['name'].rsplit('-', 3)
if len(splt) != 4:
print _('%s will not be removed: it might be a custom-built kernel.') % kpkg['name']
continue
# get the version as 'uname -r' would return
uname_ver = '-'.join([
splt[1], splt[0].replace('kernel-', '', 1), splt[2], splt[3]])
if uname_ver == running:
print _('%s will not be removed: it contains the running kernel.') % kpkg['name']
continue
if self._required_by_latest(kpkg, latest):
continue
to_remove.append(kpkg)
# Now extend the list with all kernel-devel pacakges that either
# have no matching kernel installed or belong to a kernel that is to
# be removed
if not keepdevel:
toremove.extend(self._get_old_kernel_devel(kernels, toremove))
to_remove.extend(self._devel_to_remove(to_remove, kernels, latest))
for po in toremove:
self._removePkg(po)
for pkg in to_remove:
self._removePkg(pkg)
def main(self):
@ -469,16 +515,20 @@ class PackageCleanup():
sys.exit(0)
if opts.kernels:
remove_old_kernels, kernels_to_keep = self.get_kernel_options()
if not remove_old_kernels:
# Removal of the old kernels is disabled in the config file,
# nothing to do.
sys.exit(0)
if os.geteuid() != 0:
print _("Error: Cannot remove kernels as a user, must be root")
sys.exit(1)
if int(opts.kernelcount) < 1:
print _("Error: should keep at least 1 kernel!")
sys.exit(100)
if opts.auto:
self.tsflags.append('--auto')
self._remove_old_kernels(opts.kernelcount, opts.keepdevel)
self._remove_old_kernels(kernels_to_keep, opts.keepdevel)
sys.exit(0)
#~ self.run_with_package_names.add('yum-utils')
#~ if hasattr(self, 'doUtilBuildTransaction'):