From 1dfc44e72c21c074c25dff8f2832a06747c544e9 Mon Sep 17 00:00:00 2001 From: Eric Gerbier Date: Wed, 21 Nov 2012 06:19:57 -0800 Subject: Imported Upstream version 1.11 --- rpmorphan.pl | 1405 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1405 insertions(+) create mode 100755 rpmorphan.pl (limited to 'rpmorphan.pl') diff --git a/rpmorphan.pl b/rpmorphan.pl new file mode 100755 index 0000000..a034955 --- /dev/null +++ b/rpmorphan.pl @@ -0,0 +1,1405 @@ +#!/usr/bin/perl +############################################################################### +# rpmorphan.pl +# +# Copyright (C) 2006 by Eric Gerbier +# Bug reports to: gerbier@users.sourceforge.net +# $Id: rpmorphan-1.11 | rpmorphan.pl | Wed Aug 1 20:47:14 2012 +0200 | Eric Gerbier $ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +############################################################################### +use strict; +use warnings; + +use English '-no_match_vars'; + +use Getopt::Long; # arg analysis +use Pod::Usage; # man page +use POSIX qw(strftime); +use Data::Dumper; # debug + +# library +use File::Basename; +my $dirname = dirname($PROGRAM_NAME); +my $basename = basename($PROGRAM_NAME); +require $dirname . '/rpmorphan-lib.pl' + or die "problem to load rpmorphan-lib: $ERRNO\n"; + +# translations +############## +use lib q{.}; # for tests +use lib '/usr/lib/rpmorphan'; # for prod +my $lang = $ENV{'LANG'}; +my $locale_dir; +if ( $lang =~ m/fr_FR/ ) { + $locale_dir = 'locale/fr_FR/rpmorphan_trans.pl'; +} +else { + $locale_dir = 'locale/en/rpmorphan_trans.pl'; +} +## no critic(RequireCheckingReturnValueOfEval) +eval { require $locale_dir; }; +if ($EVAL_ERROR) { + warning("can not load translation file $locale_dir"); + exit; +} + +############################################################################### +# global variables +############################################################################### +## no critic(Capitalization) +## no critic(ProhibitPackageVars) + +# translations +use vars qw( %TRANS ); + +# main widget (used for mainloop in curse mode) +use vars qw( $Main_ui); + +# widget list of packages for gui +use vars qw( $W_list ); + +# to know if we are or not in mainloop +use vars qw( $Inloop ); +$Inloop = 0; + +# command line option for access_time test +my $Opt_access_time; + +# options (for load_data, from tk and curses) +my %Opt; + +# command line option for dry-run (simulation mode) +use vars qw( $Opt_dry_run ); + +# hash structures to be filled with rpm query +my %Provides; +my %Depends; +my %Requires; +my %Install_time; +my %Files; +my %Virtual; + +# list of filtered packages +use vars qw( @Liste_pac ); +## use critic +## no critic(RequireCheckingReturnValueOfEval) + +# the code should use no "print" calls +# but instead debug, warning, info calls +########################################################## +# for debuging +# dump internal structures +sub dump_struct() { + print Dumper( 'depends', \%Depends ); + print Dumper( 'virtuals', \%Virtual ); + + display_status('dump done'); + return; +} +######################################################### +{ + + # for gui, we will have to call this sub after each delete + # as it is very expensive (file access) + # we will use a cache + # + my %access_time; + +# this is a filter : we will test if any file was accessed since $access_time days +# be careful : this code can be very slow + sub access_time_filter($$$$) { + my $name = shift @_; # package name + my $rh_files = shift @_; # general hash of package files + my $access_time = shift @_; # option access-time + my $now = shift @_; # current time + + my $ret; + if ($access_time) { + + my $age; + + # test for cache + if ( exists $access_time{$name} ) { + + # get from cache + $age = $access_time{$name}; + } + else { + + # not in cache : get it from file system + my ( $last_time, $file ) = + get_last_access( $rh_files->{$name} ); + $age = diff_date( $now, $last_time ); + debug("last access on $file is $age days old"); + $access_time{$name} = $age; + } + + if ( $age > $access_time ) { + debug("keep package $name : old access ($age days)"); + $ret = 1; + } + else { + debug("skip package $name : too recent access ($age days)"); + $ret = 0; + } + } + else { + + # no filter + $ret = 1; + } + return $ret; + } +} +######################################################### +# load_data is called from tk and curses lib (callback) +sub load_data($) { + my $opt_gui = shift @_; # true if use a gui + # also use %Opt gloval var + + # phase 1 : get data and build structures + read_rpm_data( \%Opt, \%Provides, \%Install_time, \%Files, \%Depends, + \%Virtual, \%Requires ); + +# phase 2 : guess filter +# if a package is set, it will be first used, then it will use in order -all, then (guess*) +# (see filter sub) + display_status('2 : guess filter'); + my @opt_package = @{ $Opt{'package'} }; + if (@opt_package) { + @Liste_pac = @opt_package; + } + else { + @Liste_pac = rpmfilter( \%Opt, \%Provides ); + } + + # needed by access-time and install-time options + my $now = time; + + # phase 3 : excluded and install-time filter + display_status('3 : excluded and install-time'); + my $opt_install_time = ${ $Opt{'install-time'} }; + my $excluded = $Opt{'excluded'}; + my @liste_pac2; + PACKAGE: foreach my $pac (@Liste_pac) { + if ( exists $excluded->{$pac} ) { + display_status("skip $pac : excluded"); + next PACKAGE; + } + + if ($opt_install_time) { + my $diff = diff_date( $now, $Install_time{$pac} ); + if ( $opt_install_time > 0 ) { + if ( $diff < $opt_install_time ) { + display_status( + "skip $pac : too recent install ($diff days)"); + next PACKAGE; + } + } + elsif ( $diff > -$opt_install_time ) { + display_status("skip $pac : too old install ($diff days)"); + next PACKAGE; + } + } + push @liste_pac2, $pac; + } + + # note : access-time filter is a very slow method + # so we apply this filter after dependencies research + # see below access_time_filter call + + # we want the result sorted in alphabetic order + @Liste_pac = sort @liste_pac2; + display_total(); + + # phase 4 : solve dependencies + my @orphans = + search_orphans( \%Provides, \%Files, \%Depends, \%Virtual, $now, + \@Liste_pac, $Opt_access_time ); + + # display + if ($opt_gui) { + insert_gui_orphans(@orphans); + } + else { + + # just display @orphans + my $txt = join "\n", @orphans; + info($txt); + } + + display_status('ready'); + return; +} +######################################################### +# return true is $name is an orphan +sub solve($$$$) { + my $name = shift @_; # package name + my $rh_provides = shift @_; # general provide hash + my $rh_depends = shift @_; # general dependencies hash + my $rh_virtual = shift @_; # virtual package hash + + debug("(solve) : $name"); + + my $flag_orphan = 1; + PROVIDE: foreach my $prov ( @{ $rh_provides->{$name} } ) { + + # dependencies can be to "virtual" object : smtpdaemon, webclient, bootloader + # for example, try "rpm -q --whatprovides webclient" + # you will get many packages : elinks,kdebase-common,mozilla,wget,lynx ... + + # with this new algo, let's see an example + # lilo and grub provide 'bootloader' virtual, which is necessary + # If the 2 are installed, they will be shown all 2 as orphans + # but if you remove one of them, the other is now longer orphan + if ( exists $rh_virtual->{$prov} ) { + + # this feature is a virtual + my @virtuals = sort keys %{ $rh_virtual->{$prov} }; + my $nb_virtual = scalar @virtuals; + if ( $nb_virtual == 1 ) { + + # name provide a virtual and is the only one + # so it act as a real dependency + $flag_orphan = 0; + debug("$name is required by virtual @virtuals ($prov)"); + last PROVIDE; + } + else { + + # name provide virtual but is not the only one + # we consider it an orphan + debug("$name : skip virtual $prov (@virtuals)"); + next PROVIDE; + } + } + + if ( exists $rh_depends->{$prov} ) { + + # some packages depends from $prov ... + $flag_orphan = 0; + + # only in verbose mode + if ( is_verbose( \%Opt ) ) { + my @depends = sort keys %{ $rh_depends->{$prov} }; + debug("$name is required by @depends ($prov)"); + } + last PROVIDE; + } + } + return $flag_orphan; +} +######################################################### +# use the slow "rpm -e --test" command +# but is quicker if used only on 1 package (no analysis) +# return true if is an orphan +sub quicksolve($) { + my $name = shift @_; # package name + + debug("(quicksolve) : $name"); + + my $cmd = 'rpm -e --test '; + + ## no critic ( ProhibitBacktickOperators ); + my @depends = `$cmd $name 2>&1`; + ## use critic; + my $ret; + ## no critic (ProhibitMagicNumbers) + if ( $#depends == -1 ) { + + # empty output : ok + #info($name); + $ret = 1; + } + else { + $ret = 0; + debug("$name is required by @depends"); + } + return $ret; +} +########################################################## +sub quick_run($$) { + my $rh_opt = shift @_; + my $rh_excluded = shift @_; + + debug('quick_run'); + + #allow comma separated options on tags option + my @opt_package = get_from_command_line( @{ $rh_opt->{'package'} } ); + $rh_opt->{'package'} = \@opt_package; + + if ( !is_set( $rh_opt, 'fullalgo' ) ) { + ## no critic (ProhibitMagicNumbers) + my $max_packages = 5; + if ( $#opt_package <= $max_packages ) { + + # use quicksolve + PAC: foreach my $pac (@opt_package) { + if ( exists $rh_excluded->{$pac} ) { + debug("skip $pac : excluded"); + next PAC; + } + info($pac) if ( quicksolve($pac) ); + } + exit; + } + else { + debug('too much package for quicksolve algo'); + } + } + else { + debug('full algo required'); + } + return; +} +########################################################## +# resolv dependencies for all packages in ra_liste_pac +sub search_orphans($$$$$$$) { + my $rh_provides = shift @_; + my $rh_files = shift @_; + my $rh_depends = shift @_; + my $rh_virtual = shift @_; + my $now = shift @_; + my $ra_liste_pac = shift @_; # list of all packages + my $opt_access_time = shift @_; + + display_status('4 : solve'); + my @orphans; + foreach my $pac ( @{$ra_liste_pac} ) { + if ( solve( $pac, $rh_provides, $rh_depends, $rh_virtual, ) + and access_time_filter( $pac, $rh_files, $opt_access_time, $now ) ) + { + push @orphans, $pac; + } + } + return @orphans; +} +######################################################### +# remove entries from lists for a package +sub remove_a_package($) { + my $pacname = shift @_; + + # first remove dependencies + PROVIDE: foreach my $req ( @{ $Requires{$pacname} } ) { + + delete $Depends{$req}{$pacname}; + debug("delete Depends { $req } {$pacname}"); + + # suppress entry if empty + my $keys = scalar keys %{ $Depends{$req} }; + if ( !$keys ) { + delete $Depends{$req}; + debug("delete empty Depends { $req }"); + } + } + + # remove virtuals + VIRT: foreach my $prov ( @{ $Provides{$pacname} } ) { + if ( exists $Virtual{$prov} ) { + delete $Virtual{$prov}{$pacname}; + debug("delete Virtual { $prov }{ $pacname }"); + + # suppress entry if empty + my $keys = scalar keys %{ $Virtual{$prov} }; + if ( !$keys ) { + delete $Virtual{$prov}; + debug("delete empty Virtual { $prov }"); + } + } + } + + # then delete all entries + delete $Provides{$pacname}; + delete $Files{$pacname}; + delete $Install_time{$pacname}; + delete $Requires{$pacname}; + + return; +} +######################################################### +sub is_installed($) { + my $pac = shift @_; + + # rpm -q return 0 is package exists, 1 if not + system "rpm -q $pac >/dev/null 2>/dev/null"; + ## no critic (ProhibitMagicNumbers) + # cf perldoc -f system + my $code = $CHILD_ERROR >> 8; + + return !$code; +} +######################################################### +# a generic program to be called by curses or tk sub +sub remove_packages($$) { + my $ra_sel = shift @_; # list of select packages + my $ra_list = shift @_; # list of all orphans packages + + display_status('remove packages'); + my @deleted_packages; + + my $total = scalar @{$ra_sel}; + my $i = 0; + + # remove package + SELECT: foreach my $pac ( @{$ra_sel} ) { + if ( !$Opt_dry_run ) { + $i++; + + # not in dry mode : apply change + + # rpm delete + # caution : it's suppose the shell used is bourne compatible + my $cmd = "rpm -e --allmatches $pac 2>&1"; + + # comment below to test without any real suppress + ## no critic ( ProhibitBacktickOperators ); + my $output = `$cmd`; + ## use critic; + + #print "@output\n"; + + # there is some case where a delete may not work : virtuals + # for example if you have lilo and grub installed, all 2 + # will be shown as orphans, but you will be able to delete only + # the first one ... + + # we can not rely on return code : it seems to be allways 1 + # but a successful delete does not provide any output + if ($output) { + + # to be sure : the only way is to test it + if ( is_installed($pac) ) { + + write_log($output); + display_status("problem to delete $pac (see log)"); + sleep 1; + + next SELECT; + } + else { + + # reports + write_log("$i/$total $pac deleted with output : $output"); + push @deleted_packages, $pac; + } + } + else { + + # reports + write_log("$i/$total $pac deleted"); + push @deleted_packages, $pac; + } + } + + # update internal lists + remove_a_package($pac); + } + + # build a new list of all packages without the removed ones + my @new_list; + LISTE: foreach my $l (@Liste_pac) { + foreach my $pac (@deleted_packages) { + next LISTE if ( $pac eq $l ); + } + push @new_list, $l; + } + @Liste_pac = @new_list; + display_total(); + + # compute new dependencies + my $now = time; + my @orphans = + search_orphans( \%Provides, \%Files, \%Depends, \%Virtual, $now, + \@new_list, $Opt_access_time ); + + # display + insert_gui_orphans(@orphans); + + #build_summary(@new_list); + + display_status('end of cleaning'); + return; +} +######################################################### +# used to build windows's title +sub build_title() { + + my $title = 'rpmorphan version ' . get_version(); + $title .= ' (dry-run)' if $Opt_dry_run; + return $title; +} +######################################################### +# keep +# +# a way to keep a list of exclusions +######################################################### +# list content of keep file +sub list_keep($$) { + my $keep_file = shift @_; + my $display = shift @_; # display or not ? + + my @list; + if ( open my $fh, '<', $keep_file ) { + @list = <$fh>; + chomp @list; + close $fh or warning("problem to close keep file : $ERRNO"); + + info("@list") if ($display); + } + else { + warning("can not read keep file $keep_file : $ERRNO"); + } + return @list; +} +######################################################### +# empty keep file +sub zero_keep($) { + my $keep_file = shift @_; + + if ( open my $fh, '>', $keep_file ) { + close $fh or warning("problem to close keep file : $ERRNO"); + } + else { + warning("can not empty keep file $keep_file : $ERRNO"); + } + return; +} +######################################################### +# add packages names to keep file +sub add_keep($$$) { + my $keep_file = shift @_; + my $ra_list = shift @_; + my $flag_append = shift @_; # true if append, else rewrite + + my @new_list; + my $mode; + if ($flag_append) { + $mode = '>>'; + + # read current list + my @old_list = list_keep( $keep_file, 0 ); + + # check for duplicates and merge + LOOP: foreach my $add ( @{$ra_list} ) { + foreach my $elem (@old_list) { + if ( $elem eq $add ) { + debug("skip add duplicate $add"); + next LOOP; + } + } + push @new_list, $add; + debug("add keep $add"); + } + } + else { + $mode = '>'; + @new_list = @{$ra_list}; + } + + if ( open my $fh, $mode, $keep_file ) { + foreach my $elem (@new_list) { + print {$fh} $elem . "\n"; + } + close $fh or warning("problem to close keep file : $ERRNO"); + } + else { + warning("can not write to keep file $keep_file : $ERRNO"); + } + return; +} +######################################################### +# remove packages names from keep file +sub del_keep($$) { + my $keep_file = shift @_; + my $ra_list = shift @_; + + # read current list + my @old_list = list_keep( $keep_file, 0 ); + + # remove entries + my @new_list; + LOOP: foreach my $elem (@old_list) { + foreach my $del ( @{$ra_list} ) { + if ( $elem eq $del ) { + debug("del keep $del"); + next LOOP; + } + } + push @new_list, $elem; + } + + # rewrite keep file + add_keep( $keep_file, \@new_list, 0 ); + return; +} +################################################################ +sub write_log_file($) { + my $text = shift @_; + + my $logfile = '/var/log/rpmorphan.log'; + + if ( open my $fh, '>>', $logfile ) { + my @date = gmtime; + print {$fh} strftime( '%Y/%m/%d %H:%M:%S ', @date ) . $text . "\n"; + + close $fh or warning("can not close $logfile : $ERRNO"); + } + else { + display_status("can not write to $logfile : $ERRNO"); + } + return; +} +################################################################ +{ + + # store the current session log + # just to avoid a global var + my @log; + + sub write_log { + push @log, @_; + my $text = shift @_; + display_status($text); + write_log_file($text); + return; + } + + sub get_log() { + return @log; + } +} +######################################################### +# check if tk is available +sub test_tk_gui() { + + debug('test_tk_gui'); + eval { + require Tk; + require Tk::Balloon; + require Tk::Dialog; + require Tk::Frame; + require Tk::Label; + require Tk::Listbox; + require Tk::Message; + require Tk::ROText; + }; + if ($EVAL_ERROR) { + warning('can not find Tk perl module'); + return 0; + } + else { + debug('Tk ok'); + import Tk; + import Tk::Balloon; + import Tk::Dialog; + import Tk::Frame; + import Tk::Label; + import Tk::Listbox; + import Tk::Message; + import Tk::ROText; + + # should be ok + require $dirname . '/rpmorphan-tk-lib.pl' + or die "problem to load rpmorphan-tk-lib: $ERRNO\n"; + build_tk_gui(); + return 'tk'; + } +} + +######################################################### +# check if curses is available +sub test_curses_gui() { + + debug('test_curses_gui'); + eval { require Curses::UI; }; + if ($EVAL_ERROR) { + warning('can not find Curses::UI perl module'); + return 0; + } + else { + debug('curses ok'); + import Curses::UI; + + # should be ok + require $dirname . '/rpmorphan-curses-lib.pl' + or die "problem to load rpmorphan-curses-lib: $ERRNO\n"; + build_curses_gui(); + return 'curses'; + } +} +######################################################### +# general gui +######################################################### +sub test_gui() { + return ( test_tk_gui() or test_curses_gui() ); +} +######################################################### +sub get_help_text() { + + my $baratin = $TRANS{'help'}; + return $baratin; +} +######################################################### +# version +######################################################### +{ + my $version = '1.10'; + + sub get_version() { + return $version; + } +} + +######################################################### +# +# main +# +######################################################### +my $opt_verbose = 0; # default value +init_debug($opt_verbose); + +my $opt_help; +my $opt_man; +my $opt_version; +my $opt_fullalgo; +my $opt_use_cache; +my $opt_clear_cache; +my $opt_suggests; +my $opt_gui; +my $opt_tk; +my $opt_curses; + +my $opt_all; + +my $opt_guess_perl; +my $opt_guess_python; +my $opt_guess_pike; +my $opt_guess_ruby; +my $opt_guess_common; +my $opt_guess_data; +my $opt_guess_doc; +my $opt_guess_dev; +my $opt_guess_lib; +my $opt_guess_all; +my $opt_guess_custom; + +my @opt_package; +my @opt_exclude; +my $opt_install_time; + +my $opt_keep_file; +my $opt_list_keep; +my $opt_zero_keep; +my @opt_add_keep; +my @opt_del_keep; + +%Opt = ( + 'help' => \$opt_help, # help + 'man' => \$opt_man, # full help + 'verbose' => \$opt_verbose, # debug + 'dry-run' => \$Opt_dry_run, # + 'version' => \$opt_version, + 'fullalgo' => \$opt_fullalgo, + 'all' => \$opt_all, # all packages + 'guess-perl' => \$opt_guess_perl, # perl packages + 'guess-python' => \$opt_guess_python, # python packages + 'guess-pike' => \$opt_guess_pike, + 'guess-ruby' => \$opt_guess_ruby, + 'guess-common' => \$opt_guess_common, + 'guess-data' => \$opt_guess_data, + 'guess-doc' => \$opt_guess_doc, + 'guess-dev' => \$opt_guess_dev, + 'guess-lib' => \$opt_guess_lib, + 'guess-all' => \$opt_guess_all, # all guess packages + 'guess-custom' => \$opt_guess_custom, + 'package' => \@opt_package, + 'exclude' => \@opt_exclude, + 'suggests' => \$opt_suggests, + 'install-time' => \$opt_install_time, + 'access-time' => \$Opt_access_time, + 'add-keep' => \@opt_add_keep, + 'del-keep' => \@opt_del_keep, + 'list-keep' => \$opt_list_keep, + 'zero-keep' => \$opt_zero_keep, + 'keep-file' => \$opt_keep_file, + 'use-cache' => \$opt_use_cache, + 'clear-cache' => \$opt_clear_cache, + 'gui' => \$opt_gui, + 'tk' => \$opt_tk, + 'curses' => \$opt_curses, +); + +# call from this name force gui +if ( $basename =~ m/grpmorphan/ ) { + $opt_gui = 1; +} + +# first read config file +readrc( \%Opt ); + +# get arguments +Getopt::Long::Configure('no_ignore_case'); +GetOptions( + \%Opt, 'help|?', 'man', 'verbose!', + 'fullalgo!', 'version|V', 'all!', 'guess-perl!', + 'guess-python!', 'guess-pike!', 'guess-ruby!', 'guess-common!', + 'guess-data!', 'guess-doc!', 'guess-dev!', 'guess-lib!', + 'guess-all!', 'guess-custom=s', 'package=s', 'exclude=s', + 'install-time=i', 'access-time=i', 'list-keep', 'zero-keep', + 'add-keep=s', 'del-keep=s', 'use-cache!', 'clear-cache', + 'gui!', 'tk!', 'curses!', 'dry-run!', + 'suggests!', +) or pod2usage(2); + +if ($opt_help) { + pod2usage(1); +} +elsif ($opt_man) { + pod2usage( -verbose => 2 ); +} +elsif ($opt_version) { + print_version( get_version() ); + exit; +} + +# use command line argument +init_debug($opt_verbose); + +# keep file management +# set default value +$opt_keep_file = $opt_keep_file || '/var/lib/rpmorphan/keep'; +if ($opt_list_keep) { + list_keep( $opt_keep_file, 1 ); + exit; +} +if ($opt_zero_keep) { + zero_keep($opt_keep_file); + exit; +} +if (@opt_add_keep) { + my @liste_add = get_from_command_line(@opt_add_keep); + add_keep( $opt_keep_file, \@liste_add, 1 ); + exit; +} +if (@opt_del_keep) { + my @liste_del = get_from_command_line(@opt_del_keep); + del_keep( $opt_keep_file, \@liste_del ); + exit; +} + +# disable suggests option if rpm does not provide it +if ($opt_suggests) { + $opt_suggests = is_suggests(); +} + +ana_guess( \%Opt ); + +# excluded files +my %excluded; + +# first : permanent "keep" file +my @list_keep = list_keep( $opt_keep_file, 0 ); +foreach my $ex (@list_keep) { + $excluded{$ex} = 1; + debug("conf : permanent exclude $ex"); +} + +build_excluded( \%Opt, \%excluded ); + +# install-time and access-time options are only available with the full algo +if ( ($opt_install_time) or ($Opt_access_time) ) { + debug('forced full algo'); + $opt_fullalgo = 1; +} + +# if only few packages, the big algo is not necessary +# quicksolve algo will be faster +if (@opt_package) { + + quick_run( \%Opt, \%excluded ); +} + +# test for graphical interface +if ($opt_gui) { + $opt_gui = test_gui(); +} +elsif ($opt_tk) { + $opt_gui = test_tk_gui(); +} +elsif ($opt_curses) { + $opt_gui = test_curses_gui(); +} + +if ( !$opt_gui ) { + *display_status = \&debug; + *display_total = sub { }; +} + +# load and compute +load_data($opt_gui); + +# display +if ($opt_gui) { + + # this will allow status update + $Inloop = 1; + + # loop events + if ( $opt_gui eq 'curses' ) { + $Main_ui->mainloop; + } + else { + MainLoop(); + } +} + +__END__ + +=head1 NAME + +rpmorphan - find orphaned packages + +=head1 DESCRIPTION + +rpmorphan finds "orphaned" packages on your system. It determines which packages have no other +packages depending on their installation, and shows you a list of these packages. +It is clone of deborphan debian software for rpm packages. + +It will try to help you to remove unused packages, for exemple : + +- after a distribution upgrade + +- when you want to suppress packages after some tests + +=head1 SYNOPSIS + +rpmorphan [options] [targets] + +or + +grpmorphan [options] [targets] + +using grpmorphan is a short way to use "rpmorphan -gui" + +options: + + -help brief help message + -man full documentation + -V, --version print version + + -verbose verbose + -dry-run simulate package remove + -fullalgo force full algorythm + -suggests use suggested package as if required + -use-cache use cache to avoid rpm query + -clear-cache remove cache file + -gui display the graphical interface + -tk display the tk graphical interface + -curses display the curses graphical interface + + -exclude pac exclude pac from results + -install-time +/-d apply on packages which are installed before (after) d days + -access-time d apply on packages which are not been accessed for d days (slow) + +targets: + + -package pac search if pac is an orphan package + -all apply on all packages + -guess-perl apply on perl packages + -guess-python apply on python packages + -guess-pike apply on pike packages + -guess-ruby apply on ruby packages + -guess-common apply on common packages + -guess-data apply on data packages + -guess-doc apply on documentation packages + -guess-dev apply on development packages + -guess-lib apply on library packages (this is the default target if none is specified) + -guess-all apply all -guess-* options (perl, python ...) + -guess-custom regex apply the given regex to filter to package's names to filter the output + +keep file management + + -keep-file file define the keep file to be used + -list-keep list permanent exclude list + -zero-keep empty permanent exclude list + -add-keep pac add pac package to permanent exclude list + -del-keep pac remove pac package from permanent exclude list + +=head1 REQUIRED ARGUMENTS + +you should provide at least one target. this can be + +=over 8 + +=item B<-all> + +all the installed rpm package + +=item B<-package> + +one or several explicit package + +=item B<-guess-*> + +pre-selected packages groups + +=item B<-guess-custom> + +give a filter to select the packages you want study. you can use regular expressions. + +=back + +=head1 OPTIONS + +=over 8 + +=item B<-help> + +Print a brief help message and exits. + +=item B<-man> + +Print the manual page and exits. + +=item B<-version> + +Print the program release and exit. + +=item B<-verbose> + +The program works and print debugging messages. + +=item B<-dry-run> + +this is a simulation mode : rpmorphan will show you what is +the result of package removing. The window's title is modified +to show this mode. + +=item B<-use-cache> + +the rpm query may be long (10 to 30 s). If you will run an rpmorphan tool +several time, this option will allow to gain a lot of time : +it save the rpm query on a file cache (first call), then +use this cache instead quering rpm (others calls). + +=item B<-clear-cache> + +to remove cache file. Can be used with -use-cache to write +a new cache. + +=item B<-gui> + +display a graphical interface which allow to show informations, remove packages +(an internal help is provided). This is currently the same as the -tk option + +=item B<-tk> + +display a tk graphical interface which allow to show informations, remove packages +(an internal help is provided). The Tk perl module is necessary to run the gui. + +=item B<-curses> + +display a curses graphical interface which allow to show informations, remove packages +(an internal help is provided). The Curses::UI perl module is necessary to run the gui. + + +=item B<-exclude> + +this option will specify the packages to exclude from the output. +Can be used as '--exclude pac1 --exclude pac2' +or '--exclude "pac1, pac2"' + +=item B<-install-time> + +install-time is a filter on the period from the package installation date to now (in days). +if set positive, it only allow packages installed before x days. +if set negative, it only allow packages installed since x days. + +=item B<-access-time> + +access-time is designed to filter packages which have not been used since x days. + +be careful : this option will slow the program + +=item B<-fullalgo> + +for a small list of packages, rpmorphan use a different quicker methode : rpm -e --test + +this option can be used to force the use of the full algo + +=item B<-suggests> + +some rpm version offers a meta-data dependency call suggests : it is +for usefull but not necessary packages. if this option is set, the +suggested packages are used in rpmorphan as required packages. +The default value for this option is unset. + +=item B<-package> + +search if the given package(s) is(are) orphaned. +Can be used as '--package pac1 --package pac2' +or '--package "pac1, pac2"' + +=item B<-all> + +apply on all installed packages. The output should be interpreted. +For example lilo or grub are orphaned packages, but are necessary +to boot ... + +the L and L options may be useful to filter the list + +=item B<-guess-perl> + +This option tries to find perl modules. It tries to match "^perl" + +=item B<-guess-python> + +This option tries to find python modules. It tries to match "^python" + +=item B<-guess-pike> + +This option tries to find pike modules. It tries to match "^pike" + +=item B<-guess-ruby> + +This option tries to find ruby modules. It tries to match "^ruby" + +=item B<-guess-common> + +This option tries to find common packages. It tries to match "-common$" + +=item B<-guess-data> + +This option tries to find data packages. It tries to match "-data$" + +=item B<-guess-doc> + +This option tries to find documentation packages. It tries to match "-doc$" + +=item B<-guess-data> + +This option tries to find data packages. It tries to match "-data$" + +=item B<-guess-dev> + +This option tries to find development packages. It tries to match "-devel$" + +=item B<-guess-lib> + +This option tries to find library packages. It tries to match "^lib" + +=item B<-guess-all> + +This is a short to tell : Try all of the above (perl, python ...) + +=item B<-guess-custom> + +this will allow you to specify your own filter. for exemple "^wh" +will match whois, whatsnewfm ... + +=item B<-keep-file> + +define the keep file to be used. If not set, the /var/lib/rpmorphan/keep will be used + +=item B<-list-keep> + +list the permanent list of excluded packages and exit. + +=item B<-zero-keep> + +empty the permanent list of excluded packages and exit. + +=item B<-add-keep> + +add package(s) to the permanent list of excluded packages and exit. + +Can be used as '--add-keep pac1 --add-keep pac2' +or '--add-keep "pac1, pac2"' + +=item B<-del-keep> + +remove package(s) from the permanent list of excluded packages and exit. + +Can be used as '--add-keep pac1 --add-keep pac2' +or '--add-keep "pac1, pac2"' + +=back + +=head1 USAGE + +rpmorphan can be useful after a distribution upgrade, to remove packages forgotten +by the upgrade tool. It is interesting to use the options "-all -install-time +xx'. + +If you want to remove some recent tested packages, my advice is "-all -install-time -xx'. + +if you just want to clean your disk, use '-all -access-time xxx' + +=head1 FILES + +/var/lib/rpmorphan/keep : the permanent exclude list + +/tmp/rpmorphan.cache : cache file to store rpm query. The cache +file is common to all rpmorphan tools + +/var/log/rpmorphan.log : log of all deleted packages + +=head1 CONFIGURATION + +the program can read rcfile if some exists. +it will load in order + +/etc/rpmorphanrc + +~/.rpmorphanrc + +.rpmorphanrc + +In this file, + +# are comments, + +and parameters are stored in the following format : +parameter = value + +example : + +all = 1 + +curses = 1 + +=head1 DEPENDENCIES + +rpmorphan uses standard perl module in console mode. + +If you want to use the Tk graphical interface, you should install the Tk module +(perl-Tk rpm package). + +If you want to use the curses interface, you should install the Curses::UI perl module +( perl-Curses-UI rpm package). + +If you want to speed up rpmorphan, you should install the RPM2 perl module ( perl-RPM2 rpm package). +On mandriva, the URPM perl module (dependency from urpmi) will be used instead. + +=head1 EXAMPLES + +=over 8 + +=item display orphaned libraries + +rpmorphan + +=item display all orphaned packages in a curses interface + +rpmorphan --all --curses + +=item display orphaned packages, not used since one year + +rpmorphan --all -access-time +365 + +=item display all orphaned packages, installed in the last 10 days + +rpmorphan --all -install-time -10 + +=item display all orphaned packages, installed one month ago (or more) + +rpmorphan --all -install-time +30 + +=back + +=head1 BUGS AND LIMITATIONS + +Virtuals packages are not well (for now) taken in account. +Let's see an example : lilo and grub provide 'bootloader' virtual, which is +necessary for system boot. +if the 2 are installed, they will be shown all 2 as orphans (but you can not remove +the 2). +If you remove one of them, the other is now longer shown as orphan. + +the software can only work with one version of each software : +we only treat the first version seen + +=head1 INCOMPATIBILITIES + +not known + +=head1 DIAGNOSTICS + +to be written + +=head1 NOTES + +this program can be used as "normal" user to show orphans, +but you need to run it as supersuser (root) to remove packages +or apply changes in the permanent exclude list + +access-time and install-time options are "new" features, not available +in deborphan tool + +For Fedora users : +Yum offers a program called 'package-cleanup' which, +called with the '--leaves' option, can show quickly a list of +unused library packages. + +For Mandriva users : +the 'urpme --auto-orphans' command show quickly a a list of +unused library packages. + +=head1 ENVIRONMENT + +=over 8 + +=item RPMORPHAN_METHOD + +for experts only : allow to force the method used to get rpm +data. It can be set to URPM, RPM2 or 'basic' (for external rpm query) + +=back + +=head1 SEE ALSO + +=for man +\fIrpm\fR\|(1) for rpm call +.PP +\fIrpmusage\fR\|(1) +.PP +\fIrpmdep\fR\|(1) +.PP +\fIrpmduplicates\fR\|(1) +.PP +\fIrpmextra\fR\|(1) + +=for html +rpmusage(1)
+rpmdep(1)
+rpmduplicates(1)
+rpmextra(1)
+ +=head1 EXIT STATUS + +should be allways 0 + +=head1 LICENSE AND COPYRIGHT + +Copyright (C) 2006 by Eric Gerbier +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +=head1 AUTHOR + +Eric Gerbier + +you can report any bug or suggest to gerbier@users.sourceforge.net + +=cut -- cgit v1.2.3