diff --git a/docs/urpm-package-cleanup.1 b/docs/urpm-package-cleanup.1 index 6afa4ff..54af9db 100644 --- a/docs/urpm-package-cleanup.1 +++ b/docs/urpm-package-cleanup.1 @@ -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 \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" diff --git a/locale/ru/LC_MESSAGES/urpm-tools.po b/locale/ru/LC_MESSAGES/urpm-tools.po index 72feab9..b7d9982 100644 --- a/locale/ru/LC_MESSAGES/urpm-tools.po +++ b/locale/ru/LC_MESSAGES/urpm-tools.po @@ -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." diff --git a/urpm-package-cleanup.py b/urpm-package-cleanup.py index 7a1fc5d..a924d0a 100755 --- a/urpm-package-cleanup.py +++ b/urpm-package-cleanup.py @@ -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 + devel_to_remove = [] + for pkg in devel: + if self._required_by_latest(pkg, latest): + continue - 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""" + kname = pkg['name'].replace('-devel', '', 1) + if kname not in kernel_names: + devel_to_remove.append(pkg) + continue - devellist = [] - ts = rpm.TransactionSet() - mi = ts.dbMatch('provides','kernel-devel') + if kname in to_remove_names: + devel_to_remove.append(pkg) + continue - 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: - 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 + 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'):