summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Gerbier <gerbier@users.sourceforge.net>2012-11-21 06:19:57 -0800
committerAnas Nashif <anas.nashif@intel.com>2012-11-21 06:19:57 -0800
commit1dfc44e72c21c074c25dff8f2832a06747c544e9 (patch)
treed0f53149a80a1130372dc9d7d3fa7807bfa2aa30
downloadrpmorphan-1dfc44e72c21c074c25dff8f2832a06747c544e9.tar.gz
rpmorphan-1dfc44e72c21c074c25dff8f2832a06747c544e9.tar.bz2
rpmorphan-1dfc44e72c21c074c25dff8f2832a06747c544e9.zip
Imported Upstream version 1.11upstream/1.11upstream
-rw-r--r--Authors7
-rw-r--r--COPYING280
-rw-r--r--Changelog105
-rw-r--r--Makefile178
-rw-r--r--NEWS148
-rw-r--r--Readme45
-rw-r--r--Readme.fr40
-rw-r--r--Todo9
-rw-r--r--keep0
-rw-r--r--locale/en/rpmorphan_trans.pl81
-rw-r--r--locale/fr_FR/rpmorphan_trans.pl80
-rw-r--r--rpmdep.1250
-rwxr-xr-xrpmdep.pl414
-rw-r--r--rpmduplicates.1229
-rwxr-xr-xrpmduplicates.pl266
-rw-r--r--rpmextra.1222
-rwxr-xr-xrpmextra.pl425
-rw-r--r--rpmorphan-curses-lib.pl501
-rwxr-xr-xrpmorphan-lib.pl792
-rw-r--r--rpmorphan-tk-lib.pl441
-rw-r--r--rpmorphan.1489
-rw-r--r--rpmorphan.logrotate7
-rw-r--r--rpmorphan.lsm16
-rwxr-xr-xrpmorphan.pl1405
-rw-r--r--rpmorphan.spec271
-rw-r--r--rpmorphanrc.sample54
-rw-r--r--rpmusage.1358
-rwxr-xr-xrpmusage.pl567
28 files changed, 7680 insertions, 0 deletions
diff --git a/Authors b/Authors
new file mode 100644
index 0000000..35c73bf
--- /dev/null
+++ b/Authors
@@ -0,0 +1,7 @@
+this software has been written by
+Eric Gerbier<gerbier@users.sourceforge.net>
+
+It has been released under the GNU GENERAL PUBLIC LICENSE, version 2
+
+the homepage of this project is http://sourceforge.net/projects/rpmorphan
+
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..37c6105
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,280 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for non-commercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
diff --git a/Changelog b/Changelog
new file mode 100644
index 0000000..0f5cb9e
--- /dev/null
+++ b/Changelog
@@ -0,0 +1,105 @@
+2012-08-03 Eric Gerbier <gerbier@users.sourceforge.net> 1.11
+ * add suggests option
+ * add /etc/rpmorphanrc
+ * fix bug with space in filenames
+ * add env RPMORPHAN_METHOD for debugging
+
+2011-07-08 Eric Gerbier <gerbier@users.sourceforge.net> 1.10
+ * bugfix eval for Curses (mandriva)
+ * fix bad version display
+ * clean all scripts
+ * reduce size/duplicated code in rpmorphan/rpmusage
+ * reduce menu length in curses gui (< 72)
+ * display package summary in a popup on S key
+
+2011-03-21 Eric Gerbier <gerbier@users.sourceforge.net> 1.9
+ * fix perlcritic warnings
+ * add grpmorphan as link to rpmorphan -gui
+ * add rpmextra tool
+
+2010-04-30 Eric Gerbier <gerbier@users.sourceforge.net> 1.8
+ * fix bug on rpmdep ( missing installtime in urpm mode)
+ * add help translation (gui)
+
+2010-03-01 Eric Gerbier <gerbier@users.sourceforge.net> 1.7
+ * Curses is no more an rpm dependency
+ * add reload button
+
+2010-01-20 Eric Gerbier <gerbier@users.sourceforge.net> 1.6
+ * curses window size is now variable to use full available size
+ * split core and gui code (new rpmorphan-curses-lib.pl rpmorphan-tk-lib.pl)
+ * only add "remove" button in root mode on curses interface
+ * add a confirm dialog box on remove action
+ * add sub is_remove_allowed (rpmorphan-lib.pl)
+ * rename sub filter into rpmfilter (rpmorphan-lib.pl)
+ * info window is now scrollable (curses gui)
+
+2009-11-27 Eric Gerbier <gerbier@users.sourceforge.net> 1.5
+ * add logrotate config
+ * remove all matching packages (same name)
+ * fix a bug on curses interface
+ * merge spec file with fedora one
+ * add info for fedora/mandriva users in package description
+ * add Readme.fr translation
+ * can use RPM2 perl package for faster database access
+ * use URPM perl package for faster database access on mandriva
+
+2009-01-05 Eric Gerbier <gerbier@users.sourceforge.net> 1.4
+ * improve diagnostic if "rpm -e" return some comments
+
+2008-10-19 Eric Gerbier <gerbier@users.sourceforge.net> 1.3
+ * fix a bug if exclude are set in config file (thanks Szymon Siwek)
+ * display number of deleted file
+
+2008-04-07 Eric Gerbier <gerbier@users.sourceforge.net> 1.2
+ * write log to /var/log/rpmorphan.log
+ * add rpmdep.pl tool
+ * add rpmduplicate.pl tool
+
+2007-11-22 Eric Gerbier <gerbier@users.sourceforge.net> 1.1
+ * (rpmorphan) add a default target : guess-lib
+ * (rpmorphan) test rpm delete
+ * fix warnings from perlcritic tool
+ * (rpmorphan) split too complex code (add read_one_rc, init_cache)
+ * (rpmorphan) add keep-file option, which allow to cutomize the keep file
+ * add rpmorphan-lib.pl to store common code for rpmorphan, rpmusage
+ * (rpmusage) add a default target : all
+
+2007-04-27 Eric Gerbier <gerbier@users.sourceforge.net> 1.0
+ * fix bug with tk : core dump because update call before mainloop
+ * add w_ prefix for widgets variables
+ * add dry-run (simulation) mode
+ * (gui) add 3 counters : packages, orphans, selected
+ * (gui) display_status call debug sub
+ * new Liste_pac global variable
+ * (gui) change curses geometry to work on console
+ * (gui) recursive analysis
+ * improve virtuals : now check how many package provide this one
+
+2007-03-29 Eric Gerbier <gerbier@users.sourceforge.net> 0.9
+ * add curses interface
+ * test for root access before removing packages
+ * add optionnal configuration file
+ * recode debugging system
+ * apply conway coding rules
+ * add tk_get_current_elem sub
+ * add get_from_command_line sub
+ * add status widget
+
+2007-03-08 Eric Gerbier <gerbier@users.sourceforge.net> 0.8
+ * add simple graphical user interface (optionnal)
+ * remove global variable opt_verbose
+ * split code in functions : access_time_filter, read_rpm_data, search_orphans
+
+2007-02-28 Eric Gerbier <gerbier@users.sourceforge.net> 0.4
+ * add optionnal cache
+
+2007-02-03 Eric Gerbier <gerbier@users.sourceforge.net> 0.3
+ * add rpmusage.pl tool
+ * add a link from rpmorphan.pl to rpmorphan
+
+2007-01-30 Eric Gerbier <gerbier@users.sourceforge.net> 0.2
+ * add permanent list of exclude
+
+2007-01-23 Eric Gerbier <gerbier@users.sourceforge.net> 0.1
+ * initial release
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0c79441
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,178 @@
+# $Id: rpmorphan-1.11 | Makefile | Wed Aug 1 14:09:35 2012 +0200 | Eric Gerbier $
+# recommanded by debian install guide
+DESTDIR=
+
+PACKAGE=rpmorphan
+
+SHELL = /bin/sh
+VERSION=$(shell grep Version rpmorphan.lsm | awk '{print $$2}')
+
+BINDIR = $(DESTDIR)/usr/bin
+LOGETC = $(DESTDIR)/etc/
+LOGROT = $(LOGETC)/logrotate.d/
+MANDIR = $(DESTDIR)/usr/share/man
+MAN1DIR = $(MANDIR)/man1
+# on some distribution it is $(PACKAGE)-$(VERSION) (fedora), on others $(PACKAGE) (mandriva)
+# we force it with a define in rpm target to be able to build on any host the same way
+DOCDIR=$(DESTDIR)/usr/share/doc/$(PACKAGE)-$(VERSION)
+# for keep file
+DATADIR=$(DESTDIR)/var/lib/rpmorphan
+# locales
+TARGET_LOCALE=$(DESTDIR)/usr/lib/rpmorphan/locale
+
+# log
+LOGDIR=$(DESTDIR)/var/log
+LOGFILE=rpmorphan.log
+
+DOC = Authors Changelog COPYING NEWS Todo $(PACKAGE).lsm $(PACKAGE).spec Makefile Readme Readme.fr rpmorphanrc.sample
+DATA = keep
+CONF = rpmorphan.logrotate
+
+# others programs
+PROG2=rpmusage rpmdep rpmduplicates rpmextra
+LIB=$(PACKAGE)-lib rpmorphan-curses-lib rpmorphan-tk-lib
+
+PROGS = $(PACKAGE) $(PROG2)
+MANPAGES1 := $(addsuffix .1, $(PROGS))
+HTMLPAGES := $(addsuffix .1.html, $(PROGS))
+SCRIPTS := $(addsuffix .pl, $(PROGS) $(LIB) )
+
+# file with keyword
+KEYWORD_FILES = $(SCRIPTS) Makefile Readme Readme.fr rpmorphanrc.sample
+
+# translations
+LANGS := en fr_FR
+LOCALE_BASE := rpmorphan_trans.pl
+LOCALES := $(foreach lang,$(LANGS),$(addprefix locale/$(lang)/,$(LOCALE_BASE)))
+
+# convert pod to other doc format
+%.1 : %.pl
+ pod2man $^ > $@
+
+%.1.html : %.pl
+ pod2html --header $^ | sed -f html.sed > $@
+ tidy -m -i -asxhtml -utf8 --doctype strict $@ || echo "tidy"
+
+# loop to check all perl scripts
+define check_perl_script
+ for fic in $(SCRIPTS);do \
+ perl -cw $$fic || exit; \
+ perlcritic --verbose 10 -3 $$fic || exit; \
+ done;
+endef
+
+# loop to make links on all programs
+define make_links
+ for fic in $(PROGS);do \
+ cd $(BINDIR) && ln -s $$fic.pl $$fic; \
+ done;
+endef
+
+# loop to install all locales
+define install_locales
+ for lang in $(LANGS); do \
+ d=$(TARGET_LOCALE)/$$lang; \
+ mkdir --parents $$d; \
+ (cd locale/$$lang && install --mode=644 $(LOCALE_BASE) $$d); \
+ done
+endef
+
+# default
+help :
+ @echo "available target are :"
+ @echo "make all : same as make install"
+ @echo "make help : this help"
+ @echo "make install : install software"
+ @echo "make installdoc : install software documentation"
+ @echo "make uninstall : remove software"
+ @echo "make uninstalldoc : remove software documentation"
+ @echo "### for project packagers only ###########"
+ @echo "make alldist : build all packages"
+ @echo "make check : check perl syntaxe"
+ @echo "make clean : remove temporary files"
+ @echo "make dist : build a tar.gz package"
+ @echo "make html : build html doc from pod"
+ @echo "make man : build man page from pod"
+ @echo "make rpm : build an rpm package"
+
+all: install
+
+# install perl scripts
+install : $(DOC) $(MANPAGES1) $(SCRIPTS)
+ mkdir -p $(BINDIR)
+ install -m 755 $(SCRIPTS) $(BINDIR)
+ $(make_links)
+ cd $(BINDIR) && ln -s rpmorphan grpmorphan
+ mkdir -p $(MAN1DIR)
+ install -m 644 ${MANPAGES1} $(MAN1DIR)
+ mkdir -p $(DATADIR)
+ install -m 644 ${DATA} $(DATADIR)
+ mkdir -p $(TARGET_LOCALE)
+ $(install_locales)
+ mkdir -p $(LOGDIR)
+ touch $(LOGDIR)/${LOGFILE}
+ chmod 640 $(LOGDIR)/${LOGFILE}
+ mkdir -p $(LOGROT)
+ install -m 644 ${CONF} $(LOGROT)/rpmorphan
+ install -m 644 rpmorphanrc.sample $(LOGETC)/rpmorphanrc
+
+# install doc
+installdoc : $(DOC)
+ mkdir -p $(DOCDIR)
+ install -m 644 ${DOC} $(DOCDIR)
+
+uninstall :
+ cd $(BINDIR) && rm $(SCRIPTS) $(PROGS)
+ cd $(MAN1DIR) && rm ${MANPAGES1}
+ rm -rf $(DATADIR)
+ rm -f $(LOGROT)/rpmorphan
+ rm -f $(LOGETC)/rpmorphanrc
+
+uninstalldoc :
+ rm -rf $(DOCDIR)
+
+################################################################################
+# targets for project packagers
+################################################################################
+
+# build all packages
+alldist : check dist rpm html
+
+# check perl script syntax
+check : $(SCRIPTS)
+ $(check_perl_script)
+
+# build man pages
+man : $(MANPAGES1)
+
+# build tar.gz package
+dist : $(DOC) man $(SCRIPTS) expand
+ mkdir $(PACKAGE)-$(VERSION)
+ cp -a ${CONF} $(DATA) locale $(DOC) $(MANPAGES1) $(SCRIPTS) $(PACKAGE)-$(VERSION)
+ tar cvfz $(PACKAGE)-$(VERSION).tar.gz $(PACKAGE)-$(VERSION)
+ rm -rf $(PACKAGE)-$(VERSION)
+ ~/bin/gensign.sh $(PACKAGE)-$(VERSION).tar.gz
+
+# build rpm package
+rpm : dist
+ rpmbuild -ta --sign --define '_docdir_fmt %%{NAME}-%%{VERSION}' $(PACKAGE)-$(VERSION).tar.gz
+
+# clean temp files
+clean : unexpand
+ rm -f $(MANPAGES1)
+ rm -f $(PACKAGE)-$(VERSION).tar.gz $(PACKAGE)-$(VERSION).tar.gz.sig
+ rm -f pod2*
+ rm -f *.html
+
+# build man page in html for web site
+html : $(HTMLPAGES)
+ mv *.html ../web
+
+# expand svn keywords just for publish
+expand: $(KEYWORD_FILES)
+ git tag -f "$(PACKAGE)-$(VERSION)"
+ git-svn-keyword-expand $(KEYWORD_FILES)
+
+# remove svn keywords to keep repository clean
+unexpand: $(KEYWORD_FILES)
+ git-svn-keyword-unexpand $(KEYWORD_FILES)
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..f446f50
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,148 @@
+ 1.11 version
+ -------------
+new features :
+* add suggests option
+* add /etc/rpmorphanrc
+* add env RPMORPHAN_METHOD for debugging
+
+bugfix :
+* fix bug with space in filenames
+-------------------------------------------------------------------------------
+ 1.10 version
+ -------------
+new features :
+* display package summary in a popup on S key
+
+bugfix :
+* bugfix eval for Curses (mandriva)
+* fix bad version display
+
+improvment :
+* reduce size/duplicated code in rpmorphan/rpmusage
+* reduce menu length in curses gui (< 72)
+
+others :
+* clean all scripts
+-------------------------------------------------------------------------------
+ 1.9 version
+ -------------
+new features :
+- add rpmextra tool
+- add grpmorphan as link to rpmorphan -gui
+others :
+- fix perlcritic warnings
+-------------------------------------------------------------------------------
+ 1.8 version
+ -------------
+bugfix :
+- fix bug on rpmdep ( missing installtime in urpm mode)
+
+new features :
+- add help translation (gui)
+-------------------------------------------------------------------------------
+ 1.7 version
+ -------------
+bugfix :
+- Curses is no more an rpm dependency
+
+new features :
+- add reload button
+-------------------------------------------------------------------------------
+ 1.6 version
+ -------------
+other :
+- add a confirm dialog box on remove action
+- curses window size is now variable to use full available size
+- info window is now scrollable (curses gui)
+-------------------------------------------------------------------------------
+ 1.5 version
+ -------------
+new features :
+- use URPM perl package for faster database access on mandriva
+- can use RPM2 perl package for faster database access (fedora)
+bugfix :
+- fix a bug on curses interface
+- remove all matching packages
+others :
+- merge spec file with fedora one
+- add info for fedora/mandriva users
+-------------------------------------------------------------------------------
+ 1.4 version
+ -------------
+other :
+when removing a package, anf if rpm send some output, the code now check the rpm
+database to know if the remove works or not
+-------------------------------------------------------------------------------
+ 1.3 version
+ -------------
+incompatibility : none
+bugfix :
+- fix a bug if exclude are set in config file (thanks Szymon Siwek)
+new features :
+- display number of deleted file
+other :
+-------------------------------------------------------------------------------
+ 1.2 version
+ -------------
+incompatibility : none
+bugfix : none
+new features :
+- write log to /var/log/rpmorphan.log
+- add rpmdep.pl tool
+- add rpmduplicate.pl tool
+other :
+-------------------------------------------------------------------------------
+ 1.1 version
+ -------------
+incompatibility : none
+bugfix : none
+new features :
+- (rpmorphan) add a default target : guess-lib
+- (rpmorphan) add keep-file option, which allow to cutomize the keep file
+- (rpmusage) add a default target : all
+other :
+- the different tools now share a common library
+- rpmlint was used to improve the rpm package
+
+-------------------------------------------------------------------------------
+ 1.0 version
+ -------------
+add dry-run (simulation) mode
+(gui) add 3 counters : packages, orphans, selected
+(gui) recursive analysis
+improve virtuals : now check if how many package provide this one
+-------------------------------------------------------------------------------
+ 0.9 version
+ -------------
+add another graphical interface (curses), available by the --curses command line
+argument
+add configuration file
+-------------------------------------------------------------------------------
+ 0.8 version
+ -------------
+add simple graphical user interface (optionnal), which require
+the perl-tk package :use --gui command line argument
+
+the code has been split in smaller subroutines.
+-------------------------------------------------------------------------------
+ 0.4 version
+ -------------
+add optionnal cache, which can be shared by all rpmorphan tools
+to spare rpm query time :
+use --use-cache and --clear-cache command line arguments
+-------------------------------------------------------------------------------
+ 0.3 version
+ -------------
+new feature : add rpmusage tool
+-------------------------------------------------------------------------------
+
+ 0.2 version
+ -------------
+new feature : add a permanent list of exclude file (default is empty) and
+4 new options for rpmorphan : --zero-keep, --list-keep, --add-keep, --del-keep
+
+-------------------------------------------------------------------------------
+ 0.1 version
+ -------------
+
+first public release
diff --git a/Readme b/Readme
new file mode 100644
index 0000000..367a74f
--- /dev/null
+++ b/Readme
@@ -0,0 +1,45 @@
+rpmorphan finds "orphaned"[2,5] 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 intends to be clone of
+deborphan Debian tools for rpm packages.
+
+It will try to help you to remove unused packages, for example:
+* after a distribution upgrade
+* when you want to suppress packages after some tests
+
+Several tools are also provided :
+* rpmusage - display rpm packages last use date
+* rpmdep - display the full dependency of an installed rpm package
+* rpmduplicates - find programs with several version installed
+* rpmextra - find installed packages not in distribution
+
+notes :
+*******
+
+For all users :
+[1] this rpm package will make rpmorphan work in command line mode.
+If you want to use a graphical interface, you will need to install extra
+rpm packages : perl-Tk or perl-Curses-UI.
+
+For Fedora users :
+[2] Note that orphan is used in the sense of Debian's deborphan, and
+is NOT the same as Fedora orphaned packages which are packages that
+have no current maintainer.
+
+[3] Yum offers a program called 'package-cleanup' which,
+called with the '--leaves' option, can show quickly a list of
+unused library packages.
+
+[4] rpmorphan can be speed up by installing the perl-RPM2 rpm package
+
+For Mandriva users :
+[5] the 'urpme --auto-orphans' command show quickly a a list of
+unused library packages.
+
+quick start
+***********
+rpmorphan --gui --all
+
+--
+Eric Gerbier
+$Id: rpmorphan-1.11 | Readme | Mon Mar 21 12:08:17 2011 +0000 | gerbier $
diff --git a/Readme.fr b/Readme.fr
new file mode 100644
index 0000000..e724ea2
--- /dev/null
+++ b/Readme.fr
@@ -0,0 +1,40 @@
+Le logiciel rpmorphan liste les packages rpm qui n'ont plus de
+dépendances avec les autres paquets installés sur votre système.
+C'est un clone du logiciel deborphan de debian pour les packages rpm.
+
+Il peut vous aider pour supprimer les packages inutilisés, par exemple :
+* après une montée de version système
+* lors de la suppression de logiciels après des tests
+
+plusieurs outils sont également fournis :
+* rpmusage : donne la date de la dernière utilisation d'un package
+* rpmdep : fournit l'ensemble des dependances (recursive) d'un package
+* rpmduplicates : cherche les logiciels qui ont plusieurs versions installées
+* rpmextra : cherche les packages installés hors distribution
+
+notes :
+*******
+Pour tous les utilisateurs
+[1] ce package rpm permet de faire fonctionner rpmorphan en mode
+ligne de commande. Si vous voulez profiter d'une interface graphique,
+vous devez installer des packages rpm suplementaires : perl-Tk ou perl-Curses-UI
+
+Pour les utilisateurs de Fedora :
+[2] la notion d'orphelin est à prendre au sens du logiciel debian deborphan, et
+non pas au sens général de Fedora (packages sans mainteneur).
+
+[3] Yum contient le programme 'package-cleanup', qui appelé avec l'option --leaves,
+permet d'afficher rapidement les librairies inutilisées
+
+[4] rpmorphan peut être accéléré en installant le package rpm perl-RPM2
+
+Pour les utilisateurs de Mandriva
+[5] la commande 'urpme --auto-orphans' permet d'afficher rapidement les librairies inutilisées.
+
+demarrage rapide
+****************
+rpmorphan.pl --all --gui
+
+--
+Eric Gerbier
+$Id: rpmorphan-1.11 | Readme.fr | Mon Mar 21 12:08:17 2011 +0000 | gerbier $
diff --git a/Todo b/Todo
new file mode 100644
index 0000000..f6eeefa
--- /dev/null
+++ b/Todo
@@ -0,0 +1,9 @@
+graphical interface
+- display more informations (summary, version, install time ? )
+- search tool
+- add command line options as menus ?
+- clean display when delete rpm ?
+
+require/recommends/suggest (suse) ?
+
+translate gui menu ?
diff --git a/keep b/keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/keep
diff --git a/locale/en/rpmorphan_trans.pl b/locale/en/rpmorphan_trans.pl
new file mode 100644
index 0000000..f03300d
--- /dev/null
+++ b/locale/en/rpmorphan_trans.pl
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+###############################################################################
+# rpmorphan-curses-lib.pl
+#
+# Copyright (C) 2006 by Eric Gerbier
+# Bug reports to: gerbier@users.sourceforge.net
+# $Id: rpmorphan-curses-lib.pl 170 2010-01-14 12:13:48Z 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 vars qw( %TRANS );
+
+$TRANS{'help'} = <<'EOF';
+rpmorphan will help you to clean unused package
+it will show you orphaned packages (without any rpm dependencies to them)
+
+buttons are :
+-------------
+remove : remove selected package(s)
+info : show informations about current package
+select all : select all packages (dangerous)
+unselect all : unselect all packages
+log : display list of suppressed packages
+help : this help screen
+quit : exit rpmorphan
+
+mouse use :
+-----------
+- left click to select/unselect a package
+- right click to display rpm informations
+
+hotkeys :
+---------
+x/r : remove package
+i : show package information
+S : show package summary in a popup
+s : select all
+u : unselect all
+l : display log
+h : help
+q : quit
+
+status window
+-------------
+total : total number of packages (filtered by guess filter and exclude list)
+orphans : number of packages resolved as orphans
+selected : number of selected orphans
+
+notes
+-----
+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.
+
+website
+-------
+http://rpmorphan.sourceforge.net
+EOF
+
+###############################################################################
+1;
diff --git a/locale/fr_FR/rpmorphan_trans.pl b/locale/fr_FR/rpmorphan_trans.pl
new file mode 100644
index 0000000..a520345
--- /dev/null
+++ b/locale/fr_FR/rpmorphan_trans.pl
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+###############################################################################
+# rpmorphan-curses-lib.pl
+#
+# Copyright (C) 2006 by Eric Gerbier
+# Bug reports to: gerbier@users.sourceforge.net
+# $Id: rpmorphan-curses-lib.pl 170 2010-01-14 12:13:48Z 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 vars qw( %TRANS );
+
+$TRANS{'help'} = <<'EOF';
+rpmorphan vous aide a supprimer les packages inutiles :
+il affiche les packages orphelins (ceux dont aucun autre package ne dépends)
+
+les bouttons disponibles sont :
+-------------------------------
+remove : pour lancer la suppression du/des packages selectionnes
+info : affiche les informations sur le package courant
+select all : selectionne tous les packages (dangereux)
+unselect all : deselectionne tous les packages
+log : affiche la log (liste des packages supprimes)
+help : affiche cette aide
+quit : fin du programme
+
+utilisation de la souris :
+--------------------------
+- click gauche pour selectionner/deselectionner un package
+- click droit pour afficher les informations sur le package (info)
+
+raccourcis clavier :
+--------------------
+x/r : supprime le package
+i : affiche les infos du package
+S : affiche le sommaire du package dans un popup
+s : selectionne tous les packages
+u : deselectionne tous les packages
+l : affiche la log
+h : aide
+q : quit
+
+informations de bas de page
+---------------------------
+total : nombre de packages pris en compte
+( filtrage par les options guess et les listes d'exclusions)
+orphans : nombre de packages "orphelins"
+selected : nombre de packages selectionnes
+
+notes
+-----
+Ce programme est libre, vous pouvez le redistribuer et/ou le modifier selon les
+termes de la Licence Publique Générale GNU publiée par la Free Software Foundation
+(version 2 ou bien toute autre version ultérieure choisie par vous).
+
+Ce programme est distribué car potentiellement utile, mais SANS AUCUNE GARANTIE,
+ni explicite ni implicite, y compris les garanties de commercialisation ou d'adaptation
+dans un but spécifique. Reportez-vous à la Licence Publique Générale GNU pour plus de détails.
+
+website
+-------
+http://rpmorphan.sourceforge.net
+EOF
+
+###############################################################################
+1;
diff --git a/rpmdep.1 b/rpmdep.1
new file mode 100644
index 0000000..720dae3
--- /dev/null
+++ b/rpmdep.1
@@ -0,0 +1,250 @@
+.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" Escape single quotes in literal strings from groff's Unicode transform.
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.ie \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.el \{\
+. de IX
+..
+.\}
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "RPMDEP 1"
+.TH RPMDEP 1 "2012-08-01" "perl v5.14.2" "User Contributed Perl Documentation"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.if n .ad l
+.nh
+.SH "NAME"
+rpmdep \- display the full dependency of an installed rpm package
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+rpmdep search recursively for package dependencies.
+It resolvs all dependencies to package names.
+It can also prepare a file to build a graph of dependencies, with graphviz.
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+rpmdep.pl [options] package
+.PP
+options:
+.PP
+.Vb 5
+\& \-help brief help message
+\& \-man full documentation
+\& \-V, \-\-version print version
+\& \-use\-cache use cache file instead rpm query
+\& \-clear\-cache clear cache file
+\&
+\& \-verbose verbose
+\& \-dot dotfile build a dot file for graphviz
+.Ve
+.SH "REQUIRED ARGUMENTS"
+.IX Header "REQUIRED ARGUMENTS"
+the package to resolv
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-help\fR" 8
+.IX Item "-help"
+Print a brief help message and exits.
+.IP "\fB\-man\fR" 8
+.IX Item "-man"
+Print the manual page and exits.
+.IP "\fB\-version\fR" 8
+.IX Item "-version"
+Print the program release and exit.
+.IP "\fB\-verbose\fR" 8
+.IX Item "-verbose"
+The program works and print debugging messages.
+.IP "\fB\-dot\fR" 8
+.IX Item "-dot"
+create a dot file to be used by graphviz
+.IP "\fB\-use\-cache\fR" 8
+.IX Item "-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).
+.IP "\fB\-clear\-cache\fR" 8
+.IX Item "-clear-cache"
+to remove cache file. Can be used with \-use\-cache to write
+a new cache.
+.SH "USAGE"
+.IX Header "USAGE"
+rpmdep.pl \-\-use\-cache \-dot bash.dot bash
+.PP
+dot \-Tps bash.dot \-o bash.ps
+.SH "FILES"
+.IX Header "FILES"
+the program can use the /tmp/rpmorphan.cache file
+.SH "DIAGNOSTICS"
+.IX Header "DIAGNOSTICS"
+the verbose mode allow to see all the recursive work
+.PP
+others messages are
+.IP "\fBpackage ... is not installed\fR" 8
+.IX Item "package ... is not installed"
+this is not a name of an installed package
+.IP "\fB... has no dependencies\fR" 8
+.IX Item "... has no dependencies"
+this can comes for some admin tools (ash for example)
+.IP "\fB... depends upon (list)\fR" 8
+.IX Item "... depends upon (list)"
+return a list of sorted and comma separated packages
+.SH "EXIT STATUS"
+.IX Header "EXIT STATUS"
+O if all is ok
+.PP
+>=1 if a problem
+.SH "CONFIGURATION"
+.IX Header "CONFIGURATION"
+nothing
+.SH "DEPENDENCIES"
+.IX Header "DEPENDENCIES"
+you should use graphviz to build graph from
+.SH "INCOMPATIBILITIES"
+.IX Header "INCOMPATIBILITIES"
+not known
+.SH "BUGS AND LIMITATIONS"
+.IX Header "BUGS AND LIMITATIONS"
+the program does not work well on program installed
+with several versions
+.SH "NOTES"
+.IX Header "NOTES"
+this program can be used as \*(L"normal\*(R" user
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\fIrpm\fR\|(1) for rpm call
+.PP
+\fIrpmorphan\fR\|(1)
+.PP
+\fIrpmusage\fR\|(1)
+.PP
+\fIrpmduplicates\fR\|(1)
+.PP
+\fIrpmextra\fR\|(1)
+.SH "LICENSE AND COPYRIGHT"
+.IX Header "LICENSE AND COPYRIGHT"
+Copyright (C) 2008 by Eric Gerbier
+This program is free software; you can redistribute it and/or modify
+it under the terms of the \s-1GNU\s0 General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+.SH "AUTHOR"
+.IX Header "AUTHOR"
+Eric Gerbier
+.PP
+you can report any bug or suggest to gerbier@users.sourceforge.net
diff --git a/rpmdep.pl b/rpmdep.pl
new file mode 100755
index 0000000..6991651
--- /dev/null
+++ b/rpmdep.pl
@@ -0,0 +1,414 @@
+#!/usr/bin/perl
+###############################################################################
+# rpmdep.pl
+#
+# Copyright (C) 2006 by Eric Gerbier
+# Bug reports to: gerbier@users.sourceforge.net
+# $Id: rpmorphan-1.11 | rpmdep.pl | Mon Jul 4 12:46:50 2011 +0000 | 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.
+#
+###############################################################################
+# this program search the full dependency of an installed rpm package
+###############################################################################
+use strict;
+use warnings;
+
+use English '-no_match_vars';
+
+use Getopt::Long; # arg analysis
+use Pod::Usage; # man page
+use Data::Dumper; # debug
+
+# library
+use File::Basename;
+my $dirname = dirname($PROGRAM_NAME);
+require $dirname . '/rpmorphan-lib.pl'
+ or die "problem to load rpmorphan-lib: $ERRNO\n";
+
+# the code should use no "print" calls
+# but instead debug, warning, info calls
+#########################################################
+# is to be defined because rpmorphan-lib need it
+sub display_status($) {
+ debug( shift @_ );
+ return;
+}
+##########################################################
+# resolv problems with characters in names for graphwiz
+sub norm($) {
+ my $name = shift @_;
+
+ my $norm_name = $name;
+ $norm_name =~ s/-/_/g;
+ $norm_name =~ s/\./_/g;
+ $norm_name =~ s/\+//g;
+
+ return $norm_name;
+}
+#########################################################
+# search the dependency of $name
+# no prototype because recursive
+sub solve {
+ my $name = shift @_; # package name
+ my $rh_provides = shift @_; # general provide hash
+ my $rh_depends = shift @_; # general dependencies hash
+ my $rh_files = shift @_; # general dependencies hash
+ my $rh_res = shift @_; # result cache
+ my $fh_dot = shift @_; # flag for dot
+ my $level = shift @_; # recurse level
+
+ $level++;
+
+ my $state = 0; # state ( 0 for ok)
+
+ foreach my $dep ( keys %{ $rh_depends->{$name} } ) {
+ my $debug = "(solve $level) $name -> $dep ";
+
+ # dep may be a package or a file or a virtual
+ my $pac; # searched package
+
+ # analyse and resolv dependency
+ if ( exists $rh_files->{$dep} ) {
+ debug("$dep is a package");
+ $pac = $dep;
+ }
+ elsif ( $dep =~ m/^rpmlib/ ) {
+
+ # strange case, but real, for example :
+ # rpmlib(PayloadFilesHavePrefix)
+ # rpmlib(CompressedFileNames)
+ debug( $debug . ' (skip rpmlib)' );
+ next;
+ }
+ elsif ( exists $rh_provides->{$dep} ) {
+ debug("$dep is a file");
+ $pac = $rh_provides->{$dep};
+ $debug .= " -> $pac ";
+
+ #debug($debug);
+ }
+ else {
+ warning("can not find who provide $dep");
+ $state++;
+ next;
+ }
+
+ # filter dependency
+ if ( exists $rh_res->{$pac} ) {
+
+ # already found
+ debug( $debug . ' (already found)' );
+ }
+ elsif ( $pac eq $name ) {
+
+ # no dependency on self
+ debug( $debug . ' (not on self)' );
+ }
+ else {
+ if ($fh_dot) {
+ print {$fh_dot} norm($name) . ' -> ' . norm($pac) . ";\n";
+ }
+ $rh_res->{$pac} = 1;
+ debug($debug);
+ my $sub_state =
+ solve( $pac, $rh_provides, $rh_depends, $rh_files, $rh_res,
+ $fh_dot, $level );
+ $state += $sub_state;
+ }
+
+ } # foreach
+ return $state;
+}
+#########################################################
+#
+# main
+#
+#########################################################
+my $version = '0.5';
+
+my $opt_help;
+my $opt_man;
+my $opt_version;
+my $opt_use_cache;
+my $opt_clear_cache;
+my $opt_verbose;
+
+my $opt_dot;
+
+my %opt = (
+ 'help' => \$opt_help,
+ 'man' => \$opt_man,
+ 'verbose' => \$opt_verbose,
+ 'version' => \$opt_version,
+ 'dot' => \$opt_dot,
+ 'use-cache' => \$opt_use_cache,
+ 'clear-cache' => \$opt_clear_cache,
+);
+
+Getopt::Long::Configure('no_ignore_case');
+GetOptions( \%opt, 'help|?', 'man', 'verbose', 'version|V', 'dot=s',
+ 'use-cache!', 'clear-cache' )
+ or pod2usage(2);
+
+init_debug($opt_verbose);
+
+if ($opt_help) {
+ pod2usage(1);
+}
+elsif ($opt_man) {
+ pod2usage( -verbose => 2 );
+}
+elsif ($opt_version) {
+ print_version($version);
+ exit;
+}
+
+# test if a target is set
+if ( $#ARGV != 0 ) {
+ pod2usage('need a target : package name');
+}
+my $package = $ARGV[0];
+
+# hash structures to be filled with rpm query
+my %files;
+my %install_time;
+my %provides;
+my %depends;
+my %virtual;
+my %requires;
+
+read_rpm_data( \%opt, \%provides, \%install_time, \%files, \%depends, \%virtual,
+ \%requires );
+
+if ( !exists $install_time{$package} ) {
+ warning("package $package is not installed");
+ exit 1;
+}
+
+# first we have to change order used %depends
+my %depends_from;
+foreach my $key1 ( keys %depends ) {
+ foreach my $key2 ( keys %{ $depends{$key1} } ) {
+
+ # ex key2 = afick-gui key1 = afick
+ $depends_from{$key2}{$key1} = 1;
+ }
+}
+
+# same for provides
+my %is_provided_by;
+foreach my $key1 ( keys %provides ) {
+ foreach my $key2 ( @{ $provides{$key1} } ) {
+
+ # key2 is provided by $key1 package
+ $is_provided_by{$key2} = $key1;
+ }
+}
+
+# dot graph ?
+my $fh_dot;
+if ($opt_dot) {
+
+ # open output file
+ ## no critic (RequireBriefOpen)
+ if ( open $fh_dot, '>', $opt_dot ) {
+ print {$fh_dot} "digraph \"rpmdep\" {\n";
+ }
+ else {
+ warning("can not write to $opt_dot file : $ERRNO\n");
+ $opt_dot = 0;
+ }
+}
+
+debug('2 : solve');
+my %res;
+my $state =
+ solve( $package, \%is_provided_by, \%depends_from, \%files, \%res, $fh_dot,
+ 0 );
+
+my $res = join q{,}, sort keys %res;
+if ($res) {
+ info("$package depends upon $res");
+}
+else {
+ info("$package has no dependencies");
+}
+
+if ($opt_dot) {
+ print {$fh_dot} "}\n";
+ close $fh_dot or die "problem to close dot file :$OS_ERROR\n";
+}
+
+exit $state;
+__END__
+
+=head1 NAME
+
+rpmdep - display the full dependency of an installed rpm package
+
+=head1 DESCRIPTION
+
+rpmdep search recursively for package dependencies.
+It resolvs all dependencies to package names.
+It can also prepare a file to build a graph of dependencies, with graphviz.
+
+=head1 SYNOPSIS
+
+rpmdep.pl [options] package
+
+options:
+
+ -help brief help message
+ -man full documentation
+ -V, --version print version
+ -use-cache use cache file instead rpm query
+ -clear-cache clear cache file
+
+ -verbose verbose
+ -dot dotfile build a dot file for graphviz
+
+=head1 REQUIRED ARGUMENTS
+
+the package to resolv
+
+=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<-dot>
+
+create a dot file to be used by graphviz
+
+=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.
+
+=back
+
+=head1 USAGE
+
+rpmdep.pl --use-cache -dot bash.dot bash
+
+dot -Tps bash.dot -o bash.ps
+
+=head1 FILES
+
+the program can use the /tmp/rpmorphan.cache file
+
+=head1 DIAGNOSTICS
+
+the verbose mode allow to see all the recursive work
+
+others messages are
+
+=over 8
+
+=item B<package ... is not installed>
+
+this is not a name of an installed package
+
+=item B<... has no dependencies>
+
+this can comes for some admin tools (ash for example)
+
+=item B<... depends upon (list)>
+
+return a list of sorted and comma separated packages
+
+=back
+
+=head1 EXIT STATUS
+
+O if all is ok
+
+>=1 if a problem
+
+=head1 CONFIGURATION
+
+nothing
+
+=head1 DEPENDENCIES
+
+you should use graphviz to build graph from
+
+=head1 INCOMPATIBILITIES
+
+not known
+
+=head1 BUGS AND LIMITATIONS
+
+the program does not work well on program installed
+with several versions
+
+=head1 NOTES
+
+this program can be used as "normal" user
+
+=head1 SEE ALSO
+
+=for man
+\fIrpm\fR\|(1) for rpm call
+.PP
+\fIrpmorphan\fR\|(1)
+.PP
+\fIrpmusage\fR\|(1)
+.PP
+\fIrpmduplicates\fR\|(1)
+.PP
+\fIrpmextra\fR\|(1)
+
+=for html
+<a href="rpmorphan.1.html">rpmorphan(1)</a><br />
+<a href="rpmusage.1.html">rpmusage(1)</a><br />
+<a href="rpmduplicates.1.html">rpmduplicates(1)</a><br />
+<a href="rpmextra.1.html">rpmextra(1)</a><br />
+
+=head1 LICENSE AND COPYRIGHT
+
+Copyright (C) 2008 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
+
diff --git a/rpmduplicates.1 b/rpmduplicates.1
new file mode 100644
index 0000000..9c821fc
--- /dev/null
+++ b/rpmduplicates.1
@@ -0,0 +1,229 @@
+.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" Escape single quotes in literal strings from groff's Unicode transform.
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.ie \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.el \{\
+. de IX
+..
+.\}
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "RPMDUPLICATES 1"
+.TH RPMDUPLICATES 1 "2012-08-01" "perl v5.14.2" "User Contributed Perl Documentation"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.if n .ad l
+.nh
+.SH "NAME"
+rpmduplicates \- find program with several version installed
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+sometimes, the upgrade of a system will install new packages, and \*(L"forget\*(R" to remove
+the older version. Rpmorphan do not work with this kind of unused packages. Rpmduplicates
+is the answer. I recommend to run it after each distribution upgrade
+(for example from fedora core 7 to 8).
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+rpmduplicates.pl [options]
+.PP
+options:
+.PP
+.Vb 3
+\& \-help brief help message
+\& \-man full documentation
+\& \-V, \-\-version print version
+\&
+\& \-verbose verbose
+.Ve
+.SH "REQUIRED ARGUMENTS"
+.IX Header "REQUIRED ARGUMENTS"
+nothing
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-help\fR" 8
+.IX Item "-help"
+Print a brief help message and exits.
+.IP "\fB\-man\fR" 8
+.IX Item "-man"
+Print the full manual page and exits.
+.IP "\fB\-version\fR" 8
+.IX Item "-version"
+Print the program release and exit.
+.IP "\fB\-verbose\fR" 8
+.IX Item "-verbose"
+The program works and print debugging messages.
+.SH "USAGE"
+.IX Header "USAGE"
+rpmduplicates.pl
+.SH "FILES"
+.IX Header "FILES"
+nothing
+.SH "DIAGNOSTICS"
+.IX Header "DIAGNOSTICS"
+it will show the duplicates with their version and build date
+.PP
+and suggest to remove the older one
+.PP
+example :
+.PP
+duplicate kernel-devel : 2.6.24.4\-64.fc8 (Sat Mar 29 15:36:41 2008) / 2.6.24.3\-50.fc8 (Thu Mar 20 20:43:47 2008)
+.PP
+suggest : rpm \-e kernel\-devel\-2.6.24.3\-50.fc8
+.SH "EXIT STATUS"
+.IX Header "EXIT STATUS"
+the number of duplicates packages found
+.SH "CONFIGURATION"
+.IX Header "CONFIGURATION"
+nothing
+.SH "DEPENDENCIES"
+.IX Header "DEPENDENCIES"
+rpmorphan library
+.SH "INCOMPATIBILITIES"
+.IX Header "INCOMPATIBILITIES"
+not known
+.SH "BUGS AND LIMITATIONS"
+.IX Header "BUGS AND LIMITATIONS"
+not known
+.SH "NOTES"
+.IX Header "NOTES"
+this program can be used as \*(L"normal\*(R" user
+.PP
+the gpg-pubkey packages are excluded from the result
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\fIrpm\fR\|(1) for rpm call
+.PP
+\fIrpmorphan\fR\|(1)
+.PP
+\fIrpmusage\fR\|(1)
+.PP
+\fIrpmdep\fR\|(1)
+.PP
+\fIrpmextra\fR\|(1)
+.SH "LICENSE AND COPYRIGHT"
+.IX Header "LICENSE AND COPYRIGHT"
+Copyright (C) 2008 by Eric Gerbier
+This program is free software; you can redistribute it and/or modify
+it under the terms of the \s-1GNU\s0 General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+.SH "AUTHOR"
+.IX Header "AUTHOR"
+Eric Gerbier
+.PP
+you can report any bug or suggest to gerbier@users.sourceforge.net
diff --git a/rpmduplicates.pl b/rpmduplicates.pl
new file mode 100755
index 0000000..840575e
--- /dev/null
+++ b/rpmduplicates.pl
@@ -0,0 +1,266 @@
+#!/usr/bin/perl
+###############################################################################
+#
+# Copyright (C) 2006 by Eric Gerbier
+# Bug reports to: gerbier@users.sourceforge.net
+# $Id: rpmorphan-1.11 | rpmduplicates.pl | Mon Jul 4 12:47:55 2011 +0000 | 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.
+#
+###############################################################################
+# search for packages with several versions installed
+###############################################################################
+use strict;
+use warnings;
+
+use English '-no_match_vars';
+
+use Getopt::Long; # arg analysis
+use Pod::Usage; # man page
+use Data::Dumper; # debug
+
+# library
+use File::Basename;
+my $dirname = dirname($PROGRAM_NAME);
+
+# not very usefull, but warning, debug ...
+require $dirname . '/rpmorphan-lib.pl'
+ or die "problem to load rpmorphan-lib: $ERRNO\n";
+
+my $VERSION = '0.2';
+
+my $opt_help;
+my $opt_man;
+my $opt_version;
+my $opt_verbose;
+
+my %opt = (
+ 'help' => \$opt_help,
+ 'man' => \$opt_man,
+ 'verbose' => \$opt_verbose,
+ 'version' => \$opt_version,
+);
+
+Getopt::Long::Configure('no_ignore_case');
+GetOptions( \%opt, 'help|?', 'man', 'verbose', 'version|V' )
+ or pod2usage(2);
+
+init_debug($opt_verbose);
+
+if ($opt_help) {
+ pod2usage(1);
+}
+elsif ($opt_man) {
+ pod2usage( -verbose => 2 );
+}
+elsif ($opt_version) {
+ print_version($VERSION);
+ exit;
+}
+
+# we cannot use common cache as duplicate package are not clean taken
+# by the common code
+## no critic (RequireInterpolationOfMetachars)
+my $cmd =
+ 'rpm -qa --queryformat "%{NAME} %{VERSION}-%{RELEASE} %{BUILDTIME}\n" ';
+## use critic
+
+my $fh;
+## no critic (ProhibitTwoArgOpen,RequireBriefOpen)
+if ( !open $fh, "$cmd |" ) {
+ die "can not open $cmd : $ERRNO\n";
+}
+## use critic
+
+my %h_ver; # versions
+my %h_buildtime; # buildtime
+
+my $nb = 0;
+my $SPACE = q{ };
+
+while ( my $line = <$fh> ) {
+ chomp $line;
+ my ( $soft, $version, $build ) = split $SPACE, $line;
+
+ next if ( $soft eq 'gpg-pubkey' );
+
+ if ( exists $h_ver{$soft} ) {
+
+ # we already have found a package with same name
+ my $old_ver = $h_ver{$soft};
+ my $old_date = $h_buildtime{$soft};
+
+ my $old;
+ if ( $old_date < $build ) {
+
+ # current package was build after the stored one
+ # keep the latest
+ $h_buildtime{$soft} = $build;
+
+ # delete the older
+ $old = $old_ver;
+ }
+ else {
+
+ # current package was build before the stored one
+ # delete the older
+ $old = $version;
+ }
+ $old_date = localtime $old_date;
+ $build = localtime $build;
+ print "duplicate $soft : $old_ver ($old_date) / $version ($build)\n";
+ print "suggest : rpm -e $soft-$old\n";
+ $nb++;
+ }
+ else {
+ $h_ver{$soft} = $version;
+ $h_buildtime{$soft} = $build;
+ }
+}
+close $fh or warning("can not close $cmd : $ERRNO");
+
+if ( $nb == 0 ) {
+ print "no duplicate found !\n";
+}
+
+exit $nb;
+__END__
+
+=head1 NAME
+
+rpmduplicates - find program with several version installed
+
+=head1 DESCRIPTION
+
+sometimes, the upgrade of a system will install new packages, and "forget" to remove
+the older version. Rpmorphan do not work with this kind of unused packages. Rpmduplicates
+is the answer. I recommend to run it after each distribution upgrade
+(for example from fedora core 7 to 8).
+
+=head1 SYNOPSIS
+
+rpmduplicates.pl [options]
+
+options:
+
+ -help brief help message
+ -man full documentation
+ -V, --version print version
+
+ -verbose verbose
+
+=head1 REQUIRED ARGUMENTS
+
+nothing
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<-help>
+
+Print a brief help message and exits.
+
+=item B<-man>
+
+Print the full manual page and exits.
+
+=item B<-version>
+
+Print the program release and exit.
+
+=item B<-verbose>
+
+The program works and print debugging messages.
+
+=back
+
+=head1 USAGE
+
+rpmduplicates.pl
+
+=head1 FILES
+
+nothing
+
+=head1 DIAGNOSTICS
+
+it will show the duplicates with their version and build date
+
+and suggest to remove the older one
+
+example :
+
+duplicate kernel-devel : 2.6.24.4-64.fc8 (Sat Mar 29 15:36:41 2008) / 2.6.24.3-50.fc8 (Thu Mar 20 20:43:47 2008)
+
+suggest : rpm -e kernel-devel-2.6.24.3-50.fc8
+
+=head1 EXIT STATUS
+
+the number of duplicates packages found
+
+=head1 CONFIGURATION
+
+nothing
+
+=head1 DEPENDENCIES
+
+rpmorphan library
+
+=head1 INCOMPATIBILITIES
+
+not known
+
+=head1 BUGS AND LIMITATIONS
+
+not known
+
+=head1 NOTES
+
+this program can be used as "normal" user
+
+the gpg-pubkey packages are excluded from the result
+
+=head1 SEE ALSO
+
+=for man
+\fIrpm\fR\|(1) for rpm call
+.PP
+\fIrpmorphan\fR\|(1)
+.PP
+\fIrpmusage\fR\|(1)
+.PP
+\fIrpmdep\fR\|(1)
+.PP
+\fIrpmextra\fR\|(1)
+
+=for html
+<a href="rpmorphan.1.html">rpmorphan(1)</a><br />
+<a href="rpmusage.1.html">rpmusage(1)</a><br />
+<a href="rpmdep.1.html">rpmdep(1)</a><br />
+<a href="rpmextra.1.html">rpmextra(1)</a><br />
+
+=head1 LICENSE AND COPYRIGHT
+
+Copyright (C) 2008 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
+
diff --git a/rpmextra.1 b/rpmextra.1
new file mode 100644
index 0000000..d79bf5c
--- /dev/null
+++ b/rpmextra.1
@@ -0,0 +1,222 @@
+.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" Escape single quotes in literal strings from groff's Unicode transform.
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.ie \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.el \{\
+. de IX
+..
+.\}
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "RPMEXTRA 1"
+.TH RPMEXTRA 1 "2012-08-01" "perl v5.14.2" "User Contributed Perl Documentation"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.if n .ad l
+.nh
+.SH "NAME"
+rpmextra \- display extra packages : not from distribution repositories
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+rpmextra search for extra packages :
+packages installed on the system that are not available
+in any repository listed in the config file
+.PP
+this packages are often installed manually and are not
+updated as others packages by distribution tools
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+rpmextra [options]
+.PP
+options:
+.PP
+.Vb 4
+\& \-help brief help message
+\& \-man full documentation
+\& \-V, \-\-version print version
+\& \-verbose verbose
+.Ve
+.SH "REQUIRED ARGUMENTS"
+.IX Header "REQUIRED ARGUMENTS"
+none
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-help\fR" 8
+.IX Item "-help"
+Print a brief help message and exits.
+.IP "\fB\-man\fR" 8
+.IX Item "-man"
+Print the manual page and exits.
+.IP "\fB\-version\fR" 8
+.IX Item "-version"
+Print the program release and exit.
+.IP "\fB\-verbose\fR" 8
+.IX Item "-verbose"
+The program works and print debugging messages.
+.SH "USAGE"
+.IX Header "USAGE"
+rpmextra
+.SH "DIAGNOSTICS"
+.IX Header "DIAGNOSTICS"
+the verbose mode allow to see which
+functions are used
+.SH "EXIT STATUS"
+.IX Header "EXIT STATUS"
+O if all is ok
+.PP
+>=1 if a problem
+.SH "CONFIGURATION"
+.IX Header "CONFIGURATION"
+nothing
+.SH "DEPENDENCIES"
+.IX Header "DEPENDENCIES"
+rpmextra depends upon rpm package managers.
+it can use yum (Redhat), urpmi (Mandriva), zypper (Suse) tools
+.SH "INCOMPATIBILITIES"
+.IX Header "INCOMPATIBILITIES"
+not known
+.SH "BUGS AND LIMITATIONS"
+.IX Header "BUGS AND LIMITATIONS"
+the program does not work well on program installed
+with several versions
+.SH "NOTES"
+.IX Header "NOTES"
+this program can be used as \*(L"normal\*(R" user
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\fIrpm\fR\|(1) for rpm call
+.PP
+\fIrpmorphan\fR\|(1)
+.PP
+\fIrpmusage\fR\|(1)
+.PP
+\fIrpmduplicates\fR\|(1)
+.PP
+\fIrpmdep\fR\|(1)
+.SH "LICENSE AND COPYRIGHT"
+.IX Header "LICENSE AND COPYRIGHT"
+Copyright (C) 2008 by Eric Gerbier
+This program is free software; you can redistribute it and/or modify
+it under the terms of the \s-1GNU\s0 General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+.SH "AUTHOR"
+.IX Header "AUTHOR"
+Eric Gerbier
+.PP
+you can report any bug or suggest to gerbier@users.sourceforge.net
diff --git a/rpmextra.pl b/rpmextra.pl
new file mode 100755
index 0000000..d9d4c51
--- /dev/null
+++ b/rpmextra.pl
@@ -0,0 +1,425 @@
+#!/usr/bin/perl
+###############################################################################
+# rpmextra.pl
+#
+# Copyright (C) 2006 by Eric Gerbier
+# Bug reports to: gerbier@users.sourceforge.net
+# $Id: rpmorphan-1.11 | rpmextra.pl | Mon Jul 4 12:49:43 2011 +0000 | 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.
+#
+###############################################################################
+# this program search extra packages
+# same as : yum list extra
+###############################################################################
+use strict;
+use warnings;
+
+use English '-no_match_vars';
+
+use Getopt::Long; # arg analysis
+use Pod::Usage; # man page
+use Data::Dumper; # debug
+
+# library
+use File::Basename;
+my $dirname = dirname($PROGRAM_NAME);
+require $dirname . '/rpmorphan-lib.pl'
+ or die "problem to load rpmorphan-lib: $ERRNO\n";
+
+# the code should use no "print" calls
+# but instead debug, warning, info calls
+#########################################################
+# is to be defined because rpmorphan-lib need it
+sub display_status($) {
+ debug( shift @_ );
+ return;
+}
+##########################################################
+# search for a command in PATH variable
+sub search_in_path($) {
+ my $prog = shift @_;
+
+ foreach ( split /:/, $ENV{'PATH'} ) {
+ if ( -x "$_/$prog" ) {
+ return "$_/$prog";
+ }
+ }
+ return;
+}
+##########################################################
+# get list of installed packages
+sub get_installed_rpm() {
+
+ debug('get list of installed packages');
+ ## no critic (ProhibitBacktickOperators)
+ my @list = `/bin/rpm -qa`;
+ chomp @list;
+ my @sorted_list = sort @list;
+ return @sorted_list;
+}
+#########################################################
+# for distribution using package-cleanup
+# ex : redhat/centos
+sub get_extra_cleanup($) {
+ my $cleanup_path = shift @_; # path to package-cleanup
+
+ debug('using package-cleanup');
+
+ my $cmd = "$cleanup_path -q --orphans";
+
+ # output look like :
+ # kernel-2.6.35.12-90.fc14.i686
+ # rpmorphan-1.8-1.noarch
+
+ my %hash;
+ ## no critic (ProhibitTwoArgOpen)
+ if ( open my $fh, "$cmd |" ) {
+ my $nr = 0;
+ while ( my $line = <$fh> ) {
+ chomp $line;
+
+ # skip first lines
+ if ( $nr > 1 ) {
+ $hash{$line} = 1;
+ }
+ $nr++;
+ }
+ close $fh or warning("problem on close $cmd : $ERRNO");
+ }
+ else {
+ warning("can not read $cmd output");
+ exit;
+ }
+
+ foreach my $pac ( sort keys %hash ) {
+ print "$pac\n";
+ }
+ return;
+}
+#########################################################
+# for distribution using yum
+# ex : redhat/centos/fedora
+sub get_extra_yum($) {
+ my $yum_path = shift @_; # path to yum
+
+ debug('using yum');
+ my $cmd = "$yum_path -q list extras 2>/dev/null";
+
+ # output look like :
+ # zynaddsubfx.x86_64 2.4.0-2.fc12 fedora
+ # zynjacku.x86_64 5-4.fc12 updates
+
+ my %hash;
+ ## no critic (ProhibitTwoArgOpen)
+ if ( open my $fh, "$cmd |" ) {
+ my $nr = 0;
+ while ( my $line = <$fh> ) {
+ chomp $line;
+
+ if ( $nr > 0 ) {
+
+ # line follow the format : name version repository
+ # but if version is too long, line may be splitted in 2 lines !!!
+ ## no critic (ProhibitEmptyQuotes)
+ my ( $name, $version, undef ) = split ' ', $line;
+ if ( !$version ) {
+ print "problem line $line\n";
+ }
+ else {
+ $name =~ s/\..*//; # remove arch
+ $name .= $version; # add version
+ $hash{$name} = 1;
+ }
+ }
+ $nr++;
+ }
+ close $fh or warning("problem on close $cmd : $ERRNO");
+ }
+ else {
+ warning("can not read $cmd output");
+ exit;
+ }
+
+ foreach my $pac ( sort keys %hash ) {
+ print "$pac\n";
+ }
+ return;
+}
+#########################################################
+# for distribution using urpmq
+# ex : Mandriva
+sub get_extra_urpmq($) {
+ my $urpmq_path = shift @_; # path to urpmq
+
+ # maybe use urpm perl module ?
+ debug('using urpmq');
+ my $cmd = "$urpmq_path --list -r";
+
+ # output look like :
+ # zziplib0-devel-0.13.58-1mdv2010.1
+ # zzuf-0.13-1mdv2010.1
+
+ # get list of all available packages
+ my %available;
+ debug('get list of packages in repository');
+ ## no critic (ProhibitTwoArgOpen)
+ if ( open my $fh, "$cmd |" ) {
+ while ( my $line = <$fh> ) {
+ chomp $line;
+ $available{$line} = 1;
+ }
+ close $fh or warning("problem on close $cmd : $ERRNO");
+ }
+ else {
+ warning("can not read $cmd output");
+ exit;
+ }
+
+ my @installed = get_installed_rpm();
+
+ foreach my $pac (@installed) {
+ if ( !exists $available{$pac} ) {
+ print "$pac\n";
+ }
+ }
+ return;
+}
+#########################################################
+# for distribution using zypper
+# ex : Suse
+sub get_extra_zypper($) {
+ my $zypper_path = shift @_;
+
+ debug('using zypper');
+
+ my $cmd = "$zypper_path pa";
+
+ # output look like :
+ # i | openSUSE-11.4 OSS | ConsoleKit | 0.4.3-6.1 | i586
+ # | openSUSE-11.4 OSS | ConsoleKit-devel | 0.4.3-6.1 | i586
+
+ # get list of all available packages
+ debug('get list of packages in repository');
+ my %available;
+ ## no critic (ProhibitTwoArgOpen)
+ if ( open my $fh, "$cmd |" ) {
+ while ( my $line = <$fh> ) {
+ chomp $line;
+ next unless ( $line =~ m/\|/ ); # skip first lines
+ $line =~ s/\s+//g; # remove spaces
+
+ my @tab = split /\|/, $line;
+ ## no critic(ProhibitMagicNumbers)
+ my $pac = $tab[2] . q{-} . $tab[3] . q{.} . $tab[4];
+ $available{$pac} = 1;
+ }
+ close $fh or warning("problem on close $cmd : $ERRNO");
+ }
+ else {
+ warning("can not read $cmd output");
+ exit;
+ }
+
+ my @installed = get_installed_rpm();
+
+ foreach my $pac (@installed) {
+ if ( !exists $available{$pac} ) {
+ print "$pac\n";
+ }
+ }
+
+ return;
+}
+#########################################################
+#
+# main
+#
+#########################################################
+my $version = '0.2';
+
+my $opt_help;
+my $opt_man;
+my $opt_version;
+my $opt_verbose;
+
+my %opt = (
+ 'help' => \$opt_help,
+ 'man' => \$opt_man,
+ 'verbose' => \$opt_verbose,
+ 'version|V' => \$opt_version,
+);
+
+Getopt::Long::Configure('no_ignore_case');
+GetOptions( \%opt, 'help|?', 'man', 'verbose', 'version|V', )
+ or pod2usage(2);
+
+init_debug($opt_verbose);
+
+if ($opt_help) {
+ pod2usage(1);
+}
+elsif ($opt_man) {
+ pod2usage( -verbose => 2 );
+}
+elsif ($opt_version) {
+ print_version($version);
+ exit;
+}
+
+my $yum_util_path = search_in_path('package-cleanup');
+my $yum_path = search_in_path('yum');
+my $urpmq_path = search_in_path('urpmq');
+my $zypper_path = search_in_path('zypper');
+
+## no critic (ProhibitCascadingIfElse)
+if ($yum_util_path) {
+ get_extra_cleanup($yum_util_path);
+}
+elsif ($yum_path) {
+ get_extra_yum($yum_path);
+}
+elsif ($urpmq_path) {
+ get_extra_urpmq($urpmq_path);
+}
+elsif ($zypper_path) {
+ get_extra_zypper($zypper_path);
+}
+else {
+ warning('unknown package tool');
+}
+## use critic
+
+exit;
+__END__
+
+=head1 NAME
+
+rpmextra - display extra packages : not from distribution repositories
+
+=head1 DESCRIPTION
+
+rpmextra search for extra packages :
+packages installed on the system that are not available
+in any repository listed in the config file
+
+this packages are often installed manually and are not
+updated as others packages by distribution tools
+
+=head1 SYNOPSIS
+
+rpmextra [options]
+
+options:
+
+ -help brief help message
+ -man full documentation
+ -V, --version print version
+ -verbose verbose
+
+=head1 REQUIRED ARGUMENTS
+
+none
+
+=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.
+
+=back
+
+=head1 USAGE
+
+rpmextra
+
+=head1 DIAGNOSTICS
+
+the verbose mode allow to see which
+functions are used
+
+=head1 EXIT STATUS
+
+O if all is ok
+
+>=1 if a problem
+
+=head1 CONFIGURATION
+
+nothing
+
+=head1 DEPENDENCIES
+
+rpmextra depends upon rpm package managers.
+it can use yum (Redhat), urpmi (Mandriva), zypper (Suse) tools
+
+=head1 INCOMPATIBILITIES
+
+not known
+
+=head1 BUGS AND LIMITATIONS
+
+the program does not work well on program installed
+with several versions
+
+=head1 NOTES
+
+this program can be used as "normal" user
+
+=head1 SEE ALSO
+
+=for man
+\fIrpm\fR\|(1) for rpm call
+.PP
+\fIrpmorphan\fR\|(1)
+.PP
+\fIrpmusage\fR\|(1)
+.PP
+\fIrpmduplicates\fR\|(1)
+.PP
+\fIrpmdep\fR\|(1)
+
+=for html
+<a href="rpmorphan.1.html">rpmorphan(1)</a><br />
+<a href="rpmusage.1.html">rpmusage(1)</a><br />
+<a href="rpmduplicates.1.html">rpmduplicates(1)</a><br />
+<a href="rpmdep.1.html">rpmdep(1)</a><br />
+
+=head1 LICENSE AND COPYRIGHT
+
+Copyright (C) 2008 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
+
diff --git a/rpmorphan-curses-lib.pl b/rpmorphan-curses-lib.pl
new file mode 100644
index 0000000..90c218f
--- /dev/null
+++ b/rpmorphan-curses-lib.pl
@@ -0,0 +1,501 @@
+#!/usr/bin/perl
+###############################################################################
+# rpmorphan-curses-lib.pl
+#
+# Copyright (C) 2006 by Eric Gerbier
+# Bug reports to: gerbier@users.sourceforge.net
+# $Id: rpmorphan-1.11 | rpmorphan-curses-lib.pl | Wed Jul 6 13:59:20 2011 +0000 | 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.
+#
+###############################################################################
+# curses gui code
+###############################################################################
+use strict;
+use warnings;
+
+use English '-no_match_vars';
+
+## no critic(ProhibitPackageVars)
+use vars
+ qw( $W_list $Opt_dry_run @Liste_pac $Main_ui &display_total &insert_gui_orphans );
+## use critic
+
+#use Curses; # for KEY_ENTER
+## no critic(RequireCheckingReturnValueOfEval)
+eval { require Curses; };
+if ($EVAL_ERROR) {
+ die "can not find Curses package : $EVAL_ERROR\n";
+}
+else {
+ import Curses;
+}
+## use critic
+
+#### global variable
+my $flag_info = 0; # flag for info window (avoid several calls)
+
+#########################################################
+# select all
+sub curses_select_callback($) {
+ my $widget = shift;
+
+ my $container = $widget->parent();
+ my $listbox = $container->getobj('list_id');
+
+ my $pac_list = $W_list->values();
+ my @pac2 = @{$pac_list};
+ my @sel = ( 0 .. $#pac2 );
+ $listbox->set_selection(@sel);
+
+ # update
+ $listbox->draw();
+ return;
+}
+#########################################################
+# unselect all
+sub curses_unselect_callback($) {
+ my $widget = shift;
+
+ my $container = $widget->parent();
+ my $listbox = $container->getobj('list_id');
+
+ $listbox->clear_selection();
+
+ # update
+ $listbox->draw();
+ return;
+}
+#########################################################
+# remove selected items
+sub curses_remove_callback($) {
+ my $widget = shift;
+
+ my $container = $widget->parent();
+ my $listbox = $container->getobj('list_id');
+ my @sel = $listbox->get(); # selected items
+
+ my $old_pac_list = $W_list->values();
+
+ # a confirm dialog box
+ my $yes = $Main_ui->dialog(
+ -message => "confirm remove @sel ?",
+ -buttons => [ 'yes', 'no' ],
+ -values => [ 1, 0 ],
+ -title => 'Confirm',
+ );
+
+ if ($yes) {
+ remove_packages( \@sel, $old_pac_list );
+ }
+
+ # update
+ $listbox->draw();
+
+ return;
+}
+#########################################################
+# display help window
+sub curses_help_callback($;) {
+ my $this = shift @_;
+
+ my $text = get_help_text();
+ $this->root()->dialog(
+ -message => $text,
+ -buttons => ['ok'],
+ -title => 'help',
+ );
+ return;
+}
+#########################################################
+sub curses_log_callback($;) {
+ my $this = shift @_;
+
+ my @l = get_log();
+
+ @l = ('empty') if ( !@l );
+ my $l = join "\n", @l;
+
+ $this->root()->dialog(
+ -message => $l,
+ -buttons => ['ok'],
+ -title => 'log',
+ );
+ return;
+}
+#########################################################
+# display rpm summary on the current item
+sub curses_summary_callback($) {
+ my $widget = shift;
+
+ my $listbox = $widget->parent()->getobj('list_id');
+ my $current = $listbox->get_active_value();
+
+ curses_popup( get_package_summary($current) );
+ return;
+}
+#########################################################
+# display rpm info on the current item
+sub curses_info_callback($) {
+ my $widget = shift;
+
+ # a flag to avoid a fatal error
+ # only allow one call
+ if ($flag_info) {
+ return;
+ }
+ else {
+ $flag_info++;
+ }
+
+ my $listbox = $widget->parent()->getobj('list_id');
+ my $current = $listbox->get_active_value();
+
+ my $txt;
+ if ( defined $current ) {
+ my @res = get_package_info($current);
+ $txt = join "\n", @res;
+ }
+ else {
+ $txt = 'you should select a package';
+ }
+
+ # old version
+ # but the scrollbar does not work (not focusable)
+ # my $dialog = $listbox->root()->dialog(
+ # -message => $txt,
+ # -buttons => ['ok'],
+ # -title => 'info',
+ # -vscrollbar => 'left',
+ # );
+
+ # new version with TextViewer
+ my $win = $listbox->parent();
+ my $textviewer =
+ $win->add( 'mytextviewer_id', 'TextViewer', -text => $txt, );
+
+ # any key will quit
+ $win->set_binding( \&curses_exit_text_callback, KEY_ENTER() );
+ $textviewer->focus();
+ return;
+}
+#########################################################
+# called from reload button
+sub curses_load_callback($) {
+ my $widget = shift;
+
+ load_data(1);
+ return;
+}
+#########################################################
+sub curses_exit_text_callback() {
+
+ $flag_info--;
+ my $win = $Main_ui->getobj('win1_id');
+ $win->delete('mytextviewer_id');
+ $win->draw();
+ return;
+}
+#########################################################
+#sub curses_redraw_all() {
+#
+# #while (my ($id, $object) = each %{$Main_ui->{-id2object}}) {
+# #curses_popup($id);
+# #$object->draw();
+# #}
+# my $win = $Main_ui->getobj('win1_id');
+# $Main_ui->draw();
+# $win->draw();
+# foreach my $id ('buttons_id', 'list_id', 'total_id', 'total_var_id','selected_id') {
+# my $obj = $win->getobj($id);
+# if ($obj ) {
+# curses_popup("draw $id");
+# $obj->draw();
+# }
+# }
+#
+# return;
+#}
+#########################################################
+# is called when an item is selected/unselected
+# and change a counter
+sub curses_list_callback() {
+ my $widget = shift;
+ my $listbox = $widget->parent()->getobj('list_id');
+ my @sel = $listbox->get(); # selected items
+
+ my $nb = scalar @sel;
+ my $selected = $widget->parent()->getobj('selected_val_id');
+ $selected->text($nb);
+ $selected->draw();
+
+ return;
+}
+#########################################################
+# quit gui
+sub curses_quit_callback($;) {
+ my $this = shift @_;
+ exit;
+}
+#########################################################
+# display messages as a quick popup
+# to be used as debuging tool for example
+sub curses_popup($) {
+ my $message = shift @_;
+
+ $Main_ui->status($message);
+ sleep 1;
+ $Main_ui->nostatus();
+
+ return;
+}
+
+#########################################################
+sub build_curses_gui() {
+
+ # Create the root object.
+ $Main_ui = Curses::UI->new(
+ -clear_on_exit => 0,
+ -debug => 0,
+ );
+
+ # add a window
+ my $win1 = $Main_ui->add(
+ 'win1_id',
+ 'Window',
+ -title => build_title(),
+ -titlefullwidth => 1,
+ -border => 1,
+ );
+ my $height = $win1->height();
+
+ # debug
+ #print "windows height : $height, width : " . $win1->width() . "\n";
+
+ ## no critic (ProhibitMagicNumbers)
+ my $margin =
+ 5; # 1 for top menu, 2 for status/selected and 2 for borders
+ ## use critic
+ my $min_height = 2 * $margin;
+ if ( $height < $min_height ) {
+ die "the window is too small ($height)\n";
+ }
+
+ my @buttons;
+ if ( is_remove_allowed($Opt_dry_run) ) {
+
+ # I do not know how to disable the button,
+ # so I only add it in this mode
+ push @buttons,
+ (
+ {
+ -label => 'Remove ',
+ -onpress => \&curses_remove_callback,
+ },
+ );
+
+ # Bind <x> and <r> for remove
+ $Main_ui->set_binding( sub { curses_remove_callback($W_list) }, 'x' );
+ $Main_ui->set_binding( sub { curses_remove_callback($W_list) }, 'r' );
+
+ }
+ push @buttons,
+ (
+ {
+ -label => 'Info ',
+ -onpress => \&curses_info_callback,
+ },
+ {
+ -label => 'Select_all ',
+ -onpress => \&curses_select_callback,
+ },
+ {
+ -label => 'Unselect_all ',
+ -onpress => \&curses_unselect_callback,
+ },
+ {
+ -label => 'Log ',
+ -onpress => \&curses_log_callback,
+ },
+ {
+ -label => 'reLoad ',
+ -onpress => \&curses_load_callback,
+ },
+ {
+ -label => 'Help ',
+ -onpress => \&curses_help_callback,
+ },
+ {
+ -label => 'Quit',
+ -onpress => \&curses_quit_callback,
+ },
+ );
+
+ $win1->add(
+ 'buttons_id',
+ 'Buttonbox',
+ -y => 0,
+ -buttons => \@buttons,
+ );
+
+ # listbox
+ my $list_height = $height - $margin;
+ $W_list = $win1->add(
+ 'list_id', 'Listbox',
+ -y => 1,
+ -width => 60,
+ -height => $list_height,
+ -border => 1,
+ -vscrollbar => 1,
+ -multi => 1,
+ -onselchange => \&curses_list_callback,
+ );
+
+ my $empty = q{};
+
+ # total
+ $win1->add(
+ 'total_id',
+ 'Label',
+ -text => 'total',
+ -x => 1,
+ -y => $list_height + 1,
+ );
+ my $w_total = $win1->add(
+ 'total_val_id',
+ 'TextViewer',
+ -text => $empty,
+ -height => 1,
+ -width => 5,
+ -x => 7,
+ -y => $list_height + 1,
+ );
+
+ # orphans
+ $win1->add(
+ 'orphans_id',
+ 'Label',
+ -text => 'orphans',
+ -x => 12,
+ -y => $list_height + 1,
+ );
+ my $w_orphans = $win1->add(
+ 'orphans_val_id',
+ 'TextViewer',
+ -text => $empty,
+ -height => 1,
+ -width => 5,
+ -x => 20,
+ -y => $list_height + 1,
+ );
+
+ # selected
+ $win1->add(
+ 'selected_id',
+ 'Label',
+ -text => 'selected',
+ -x => 25,
+ -y => $list_height + 1,
+ );
+ my $w_selected = $win1->add(
+ 'selected_val_id',
+ 'TextViewer',
+ -text => $empty,
+ -height => 1,
+ -width => 4,
+ -x => 35,
+ -y => $list_height + 1,
+ );
+
+ # status
+ $win1->add(
+ 'status_id',
+ 'Label',
+ -text => 'status:',
+ -x => 1,
+ -y => $list_height + 2,
+ );
+ my $w_status = $win1->add(
+ 'status_val_id',
+ 'TextViewer',
+ -text => $empty,
+ -height => 1,
+ -width => 50,
+ -x => 9,
+ -y => $list_height + 2,
+ );
+
+ # begin on list
+ $W_list->focus();
+
+ # Bind <i> for info
+ $Main_ui->set_binding( sub { curses_info_callback($W_list) }, 'i' );
+
+ # S for summary
+ $Main_ui->set_binding( sub { curses_summary_callback($W_list) }, 'S' );
+
+ # Bind <s> for select
+ $Main_ui->set_binding( sub { curses_select_callback($W_list) }, 's' );
+
+ # Bind <u> for unselect
+ $Main_ui->set_binding( sub { curses_unselect_callback($W_list) }, 'u' );
+
+ # Bind <l> for log
+ $Main_ui->set_binding( sub { curses_log_callback($W_list) }, 'l' );
+
+ # Bind <h> for help
+ $Main_ui->set_binding( sub { curses_help_callback($W_list) }, 'h' );
+
+ # Bind <L> for reload
+ $Main_ui->set_binding( sub { curses_load_callback($W_list) }, 'L' );
+
+ # Bind <q> to quit.
+ $Main_ui->set_binding( sub { exit }, 'q' );
+
+ # for debuging
+ #$Main_ui->set_binding( sub { dump_struct() }, 'd' );
+
+ # common gui subroutines with different coding
+ ##############################################
+ *insert_gui_orphans = sub {
+ $W_list->values(@_);
+ $W_list->draw();
+
+ # display orphans total
+ my $nb = scalar @_;
+ $w_orphans->text($nb);
+ $w_orphans->draw();
+
+ # reset selected
+ $w_selected->text(0);
+ $w_selected->draw();
+ };
+
+ *display_status = sub {
+ my $text = shift @_;
+ debug($text);
+
+ $w_status->text($text);
+ $w_status->draw();
+ };
+
+ *display_total = sub {
+
+ # display orphans total
+ my $nb = scalar @Liste_pac;
+ $w_total->text($nb);
+ $w_total->draw();
+ };
+
+ return;
+}
+#########################################################
+1;
diff --git a/rpmorphan-lib.pl b/rpmorphan-lib.pl
new file mode 100755
index 0000000..5eacbcc
--- /dev/null
+++ b/rpmorphan-lib.pl
@@ -0,0 +1,792 @@
+#!/usr/bin/perl
+###############################################################################
+# rpmorphan-lib.pl
+#
+# Copyright (C) 2006 by Eric Gerbier
+# Bug reports to: gerbier@users.sourceforge.net
+# $Id: rpmorphan-1.11 | rpmorphan-lib.pl | Wed Aug 1 09:36:43 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 Data::Dumper; # debug
+use File::stat; # get_last_access
+
+## no critic (RequireCheckingReturnValueOfEval)
+
+# the code should use no "print" calls
+# but use instead debug, warning, info calls
+###############################################################################
+sub nodebug {
+ return;
+}
+###############################################################################
+sub backend_debug {
+ my $text = $_[0];
+ print "debug $text\n";
+ return;
+}
+###############################################################################
+# change debug subroutine
+# this way seems better than previous one as
+# - it does not need any "global" verbose variable
+# - it suppress any verbose test (quicker)
+sub init_debug($) {
+ my $verbose = shift @_;
+
+ # to avoid error messages
+ ## no critic ( NoWarnings );
+ no warnings 'redefine';
+ ## use critic;
+
+ if ($verbose) {
+ *debug = \&backend_debug;
+ }
+ else {
+ *debug = \&nodebug;
+ }
+
+ use warnings 'all';
+ return;
+}
+###############################################################################
+# used to print warning messages
+sub warning($) {
+ my $text = shift @_;
+ warn "WARNING $text\n";
+ return;
+}
+###############################################################################
+# used to print normal messages
+sub info($) {
+ my $text = shift @_;
+ print "$text\n";
+ return;
+}
+###############################################################################
+sub print_version($) {
+ my $version = shift @_;
+ info("$PROGRAM_NAME version $version");
+ return;
+}
+#########################################################
+# used to check on option
+sub is_set($$) {
+ my $rh_opt = shift @_; # hash of program arguments
+ my $key = shift @_; # name of desired option
+
+ #debug("is_set $key");
+ my $r_value = $rh_opt->{$key};
+ return ${$r_value};
+}
+#########################################################
+sub is_verbose($) {
+ my $rh_opt = shift @_; # hash of program arguments
+
+ return is_set( $rh_opt, 'verbose' );
+}
+#########################################################
+# apply a filter on package list according program options
+sub rpmfilter($$) {
+ my $rh_opt = shift @_;
+ my $rh_list_pac = shift @_;
+
+ display_status('apply filters');
+
+ # we just want the list of keys
+ my @list = keys %{$rh_list_pac};
+
+ if ( is_set( $rh_opt, 'all' ) ) {
+ debug('all');
+ return @list;
+ }
+ else {
+ debug('guess');
+
+ my @filtered_list;
+ if ( is_set( $rh_opt, 'guess-perl' ) ) {
+ debug('guess-perl');
+ my @res = grep { /^perl/ } @list;
+ push @filtered_list, @res;
+ }
+ if ( is_set( $rh_opt, 'guess-python' ) ) {
+ my @res = grep { /^python/ } @list;
+ push @filtered_list, @res;
+ }
+ if ( is_set( $rh_opt, 'guess-pike' ) ) {
+ my @res = grep { /^pike/ } @list;
+ push @filtered_list, @res;
+ }
+ if ( is_set( $rh_opt, 'guess-ruby' ) ) {
+ my @res = grep { /^ruby/ } @list;
+ push @filtered_list, @res;
+ }
+ if ( is_set( $rh_opt, 'guess-common' ) ) {
+ my @res = grep { /-common$/ } @list;
+ push @filtered_list, @res;
+ }
+ if ( is_set( $rh_opt, 'guess-data' ) ) {
+ my @res = grep { /-data$/ } @list;
+ push @filtered_list, @res;
+ }
+ if ( is_set( $rh_opt, 'guess-doc' ) ) {
+ my @res = grep { /-doc$/ } @list;
+ push @filtered_list, @res;
+ }
+ if ( is_set( $rh_opt, 'guess-dev' ) ) {
+ my @res = grep { /-devel$/ } @list;
+ push @filtered_list, @res;
+ }
+ if ( is_set( $rh_opt, 'guess-lib' ) ) {
+ my @res = grep { /^lib/ } @list;
+ push @filtered_list, @res;
+ }
+ if ( is_set( $rh_opt, 'guess-custom' ) ) {
+ my $regex = ${ $rh_opt->{'guess-custom'} };
+ my @res = grep { /$regex/ } @list;
+ push @filtered_list, @res;
+ }
+ return @filtered_list;
+ }
+}
+#########################################################
+# difference between 2 date in unix format
+# return in days
+sub diff_date($$) {
+ my $now = shift @_; # current
+ my $time = shift @_; #
+
+ # convert from seconds to days
+ ## no critic(ProhibitParensWithBuiltins,ProhibitMagicNumbers)
+ return int( ( $now - $time ) / 86_400 );
+}
+#########################################################
+# return the date (unix format) of last access on a package
+# (scan all inodes for atime) and the name of the file
+sub get_last_access($) {
+ my $ra_files = shift @_; # array of file names
+
+ my $last_date = 0; # means a very old date for linux : 1970
+ my $last_file = q{};
+ FILE: foreach my $file ( @{$ra_files} ) {
+ next FILE unless ( -e $file );
+ my $stat = stat $file;
+ my $atime = $stat->atime();
+
+ if ( $atime > $last_date ) {
+ $last_date = $atime;
+ $last_file = $file;
+ }
+ }
+ return ( $last_date, $last_file );
+}
+#########################################################
+# depending the cache option, return a command to execute
+# - as an rpm query (piped)
+# - as a cache file to open
+sub init_cache($) {
+ my $rh_opt = shift @_; # hash of program arguments
+
+ # the main idea is to reduce the number of call to rpm in order to gain time
+ # the ';' separator is used to separate fields
+ # the ',' separator is used to separate data in fields arrays
+
+ # note : we do not ask for PROVIDEFLAGS PROVIDEVERSION
+ # REQUIREFLAGS REQUIREVERSION
+
+ my $suggests = ( is_set( $rh_opt, 'suggests' ) ) ? '[%{SUGGESTS},]' : q{};
+
+ my $rpm_cmd =
+"rpm -qa --queryformat '%{NAME};[%{REQUIRENAME},]$suggests;[%{PROVIDES},];[%{FILENAMES},];%{INSTALLTIME}\n' 2>/dev/null";
+ my $cache_file = '/tmp/rpmorphan.cache';
+ my $fh_cache;
+ my $cmd;
+
+ if ( is_set( $rh_opt, 'clear-cache' ) ) {
+ unlink $cache_file if ( -f $cache_file );
+ }
+
+ if ( is_set( $rh_opt, 'use-cache' ) ) {
+ if ( -f $cache_file ) {
+
+ # cache exists : use it
+ $cmd = $cache_file;
+ display_status("use cache file $cache_file");
+ }
+ else {
+
+ # use rpm command
+ $cmd = "$rpm_cmd |";
+
+ # and create cache file
+ ## no critic (RequireBriefOpen)
+ if ( open $fh_cache, '>', $cache_file ) {
+ display_status("create cache file $cache_file");
+ }
+ else {
+ warning("can not create cache file $cache_file : $ERRNO");
+ }
+ }
+ }
+ else {
+
+ # output may be long, so we use a pipe to avoid to store a big array
+ $cmd = "$rpm_cmd |";
+ display_status('read rpm data');
+
+ unlink $cache_file if ( -f $cache_file );
+ }
+
+ return ( $cmd, $fh_cache );
+}
+#########################################################
+## no critic (ProhibitManyArgs)
+sub analyse_rpm_info($$$$$$$$$$) {
+ my $name = shift @_; # current package name
+ my $ra_prov = shift @_; # data from database on name
+ my $ra_req = shift @_; # idem
+ my $ra_files = shift @_; # idem
+ my $rh_objects = shift @_;
+ my $rh_provides = shift @_;
+ my $rh_files = shift @_;
+ my $rh_depends = shift @_;
+ my $rh_virtual = shift @_;
+ my $rh_requires = shift @_;
+
+ ## use critic;
+
+ # we do not use version in keys
+ # so we only keep the last seen data for a package name
+ if ( exists $rh_provides->{$name} ) {
+ debug("duplicate package $name");
+ }
+
+ VIRTUAL: foreach my $p ( @{$ra_prov}, @{$ra_files} ) {
+
+ # do not add files
+ #next VIRTUAL if ( $p =~ m!^/! );
+
+ # some bad package may provide more than on time the same object
+ if ( ( exists $rh_objects->{$p} )
+ && ( $rh_objects->{$p} ne $name ) )
+ {
+
+ # method 1 (old)
+ # we do not use who provide this virtual for now
+ #$rh_virtual->{$p} = 1;
+
+ # method 2 (current)
+ # to improve the code, we can use virtual as a counter
+ # ( as a garbage collector )
+ # so we can remove all providing except the last one
+ if ( !exists $rh_virtual->{$p} ) {
+
+ # add previous data
+ $rh_virtual->{$p}{ $rh_objects->{$p} } = 1;
+ }
+
+ # add new virtual
+ $rh_virtual->{$p}{$name} = 1;
+ }
+ else {
+
+ # keep memory of seen "provided"
+ $rh_objects->{$p} = $name;
+ }
+ }
+
+ # $name package provide @prov
+ # file are also included in "provide"
+ push @{ $rh_provides->{$name} }, @{$ra_prov}, @{$ra_files};
+
+ # list are necessary for access-time option
+ push @{ $rh_files->{$name} }, @{$ra_files};
+
+ # this will be helpfull when recursive remove package
+ push @{ $rh_requires->{$name} }, @{$ra_req};
+
+ # build a hash for dependencies
+ foreach my $require ( @{$ra_req} ) {
+
+ # we have to suppress auto-depends (ex : ark package)
+ my $flag_auto = 0;
+ PROVIDE: foreach my $p ( @{$ra_prov} ) {
+ if ( $require eq $p ) {
+ $flag_auto = 1;
+
+ #debug("skip auto-depency on $name");
+ last PROVIDE;
+ }
+ }
+
+ # $name depends from $require
+ # exemple : depends { 'afick' } { afick-gui } = 1
+ # push @{ $rh_depends->{$require} }, $name unless $flag_auto;
+ # we use a hash to help when we have to delete data (on recursive remove)
+ $rh_depends->{$require}{$name} = 1 unless $flag_auto;
+ }
+ return;
+}
+#########################################################
+# read rpm information about all installed packages
+# can be from database, or from rpmorphan cache
+sub read_rpm_data_base($$$$$$$) {
+ my $rh_opt = shift @_; # hash of program arguments
+ my $rh_provides = shift @_;
+ my $rh_install_time = shift @_;
+ my $rh_files = shift @_;
+ my $rh_depends = shift @_;
+ my $rh_virtual = shift @_;
+ my $rh_requires = shift @_;
+
+ display_status('read rpm data using base RPM');
+ my ( $cmd, $fh_cache ) = init_cache($rh_opt);
+
+ # because we can open a pipe or a cache, it is not possible to use
+ # the 3 arg form of open
+ ## no critic (ProhibitTwoArgOpen,RequireBriefOpen);
+ my $fh;
+ if ( !open $fh, $cmd ) {
+
+ # no critic;
+ die "can not open $cmd : $ERRNO\n";
+ ## use critic
+ }
+ debug('1 : analysis');
+ my %objects;
+ while (<$fh>) {
+
+ # write cache
+ print {$fh_cache} $_ if ($fh_cache);
+
+ my ( $name, $req, $prov, $files, $install_time ) = split /;/, $_;
+
+ # install time are necessary for install-time option
+ $rh_install_time->{$name} = $install_time;
+
+ # see rpm query format in init_cache
+ my $delim = q{,};
+ my @prov = split /$delim/, $prov;
+ my @req = split /$delim/, $req;
+ my @files = split /$delim/, $files;
+
+ analyse_rpm_info(
+ $name, \@prov, \@req, \@files,
+ \%objects, $rh_provides, $rh_files, $rh_depends,
+ $rh_virtual, $rh_requires
+ );
+
+ }
+ close $fh or warning("problem to close rpm command : $ERRNO");
+
+ # close cache if necessary
+ if ($fh_cache) {
+ close $fh_cache or warning("problem to close cache file : $ERRNO");
+ }
+ return;
+}
+#########################################################
+# read database info by use of RPM2 module
+sub read_rpm_data_rpm2($$$$$$$) {
+ my $rh_opt = shift @_; # hash of program arguments
+ my $rh_provides = shift @_;
+ my $rh_install_time = shift @_;
+ my $rh_files = shift @_;
+ my $rh_depends = shift @_;
+ my $rh_virtual = shift @_;
+ my $rh_requires = shift @_;
+
+ # cache ?
+ # acces to data by rpm2 should be fast,
+ # so do not need cache
+ # and init_cache works with a command to exec
+ # not with a perl module
+
+ display_status('read rpm data using RPM2');
+ import RPM2;
+ my $db = RPM2->open_rpm_db();
+
+ my $flag_suggests = is_set( $rh_opt, 'suggests' );
+
+ debug('1 : analysis');
+ my %objects;
+
+ my $i = $db->find_all_iter();
+ while ( my $pkg = $i->next ) {
+
+ my $name = $pkg->name;
+ my $install_time = $pkg->installtime;
+ $rh_install_time->{$name} = $install_time;
+
+ my @req = $pkg->requires;
+ my @prov = $pkg->provides;
+ my @files = $pkg->files;
+
+ if ($flag_suggests) {
+ my @suggests = $pkg->tag('SUGGESTS');
+
+ #debug("suggests for $name : " . join ' ', @suggests);
+ push @req, @suggests;
+ }
+
+ analyse_rpm_info(
+ $name, \@prov, \@req, \@files,
+ \%objects, $rh_provides, $rh_files, $rh_depends,
+ $rh_virtual, $rh_requires
+ );
+ }
+ return;
+}
+#########################################################
+# URPM return dependencies as perl[ = 5.8]
+# we have to suppress the version and only keep the package
+sub clean_rel($) {
+ my $ra = shift @_;
+
+ foreach my $elem ( @{$ra} ) {
+ $elem =~ s/\[.*\]//;
+ }
+ return;
+}
+#########################################################
+# read database info by use of URPM module
+sub read_rpm_data_urpm($$$$$$$) {
+ my $rh_opt = shift @_; # hash of program arguments
+ my $rh_provides = shift @_;
+ my $rh_install_time = shift @_;
+ my $rh_files = shift @_;
+ my $rh_depends = shift @_;
+ my $rh_virtual = shift @_;
+ my $rh_requires = shift @_;
+
+ # cache ?
+ # acces to data by rpm2 should be fast,
+ # so do not need cache
+ # and init_cache works with a command to exec
+ # not with a perl module
+
+ display_status('read rpm data using URPM');
+ import URPM;
+ my $db = URPM::DB::open();
+ debug('1 : analysis');
+
+ my $flag_suggests = is_set( $rh_opt, 'suggests' );
+
+ my %objects;
+ $db->traverse(
+ sub {
+ my ($package) = @_; # this is a URPM::Package object
+ my $name = $package->name;
+ my $installtime = $package->queryformat('%{INSTALLTIME}');
+ $rh_install_time->{$name} = $installtime;
+ my @req = $package->requires();
+ clean_rel( \@req );
+ my @prov = $package->provides();
+ clean_rel( \@prov );
+ my @files = $package->files();
+
+ if ($flag_suggests) {
+ my $suggests = $package->queryformat('[%{SUGGESTS},]');
+ my @suggests = split /,/, $suggests;
+
+ #debug("suggests for $name : " . join ' ', @suggests);
+ clean_rel( \@suggests );
+ push @req, @suggests;
+ }
+
+ analyse_rpm_info(
+ $name, \@prov, \@req, \@files,
+ \%objects, $rh_provides, $rh_files, $rh_depends,
+ $rh_virtual, $rh_requires
+ );
+ }
+ );
+
+ return;
+}
+#########################################################
+sub read_rpm_data($$$$$$$) {
+ my $rh_opt = shift @_; # hash of program arguments
+ my $rh_provides = shift @_;
+ my $rh_install_time = shift @_;
+ my $rh_files = shift @_;
+ my $rh_depends = shift @_;
+ my $rh_virtual = shift @_;
+ my $rh_requires = shift @_;
+
+ # empty all structures
+ %{$rh_provides} = ();
+ %{$rh_install_time} = ();
+ %{$rh_files} = ();
+ %{$rh_depends} = ();
+ %{$rh_virtual} = ();
+ %{$rh_requires} = ();
+
+ my %code = (
+ 'URPM' => \&read_rpm_data_urpm,
+ 'RPM2' => \&read_rpm_data_rpm2,
+ 'basic' => \&read_rpm_data_base,
+ );
+ my @def_list = ( 'URPM', 'RPM2', 'basic' );
+ my @list;
+ if ( exists $ENV{'RPMORPHAN_METHOD'} ) {
+ my $method = $ENV{'RPMORPHAN_METHOD'};
+
+ # should be 'URPM', 'RPM2' or 'basic'
+ if ( exists $code{$method} ) {
+ push @list, $method;
+
+ # for security : basic will allways work
+ push @list, 'basic';
+ }
+ else {
+ warning("unknown method $method, use default");
+ @list = @def_list;
+ }
+ }
+ else {
+
+ # default list (ordered) of perl packages to test
+ @list = @def_list;
+ }
+ my $ok = 0;
+ foreach my $method (@list) {
+ debug("try $method");
+ eval { require $method . '.pm'; };
+ if ($EVAL_ERROR) {
+ debug("can not use $method");
+ }
+ else {
+ $ok = 1;
+ *read_rpm_data_m = $code{$method};
+ read_rpm_data_m( $rh_opt, $rh_provides, $rh_install_time, $rh_files,
+ $rh_depends, $rh_virtual, $rh_requires );
+ last;
+ }
+ }
+ if ( !$ok ) {
+
+ # if nothing is working, use default basic method
+ read_rpm_data_base( $rh_opt, $rh_provides, $rh_install_time, $rh_files,
+ $rh_depends, $rh_virtual, $rh_requires );
+ }
+ return;
+}
+## use critic
+#########################################################
+sub read_one_rc($$$) {
+ my $rh_list = shift @_; # list of available parameters
+ my $fh_rc = shift @_;
+ my $rcfile = shift @_;
+
+ # perl cookbook, 8.16
+ my $line = 1;
+ RC: while (<$fh_rc>) {
+ chomp;
+ s/#.*//; # comments
+ s/^\s+//; # skip spaces
+ s/\s+$//;
+ next RC unless length;
+ my ( $key, $value ) = split /\s*=\s*/, $_, 2;
+ if ( defined $key ) {
+ if ( exists $rh_list->{$key} ) {
+
+ # the last line wins
+ if ( ref( $rh_list->{$key} ) eq 'ARRAY' ) {
+ @{ $rh_list->{$key} } = get_from_command_line($value);
+ }
+ else {
+ ${ $rh_list->{$key} } = $value;
+ }
+
+ # special case : verbose will modify immediately behavior
+ init_debug($value) if ( $key eq 'verbose' );
+
+ debug(
+"rcfile ($rcfile) : found $key parameter with value : $value"
+ );
+ }
+ else {
+ warning("bad $key parameter in line $line in $rcfile file");
+ }
+ }
+ else {
+ warning("bad line $line in $rcfile file");
+ }
+ $line++;
+ }
+
+ return;
+}
+#########################################################
+# read all existing rc file from general to local :
+# host, home, local directory
+sub readrc($) {
+
+ my $rh_list = shift @_; # list of available parameters
+
+ # can use local rc file, home rc file, host rc file
+ my @list_rc =
+ ( '/etc/rpmorphanrc', $ENV{HOME} . '/.rpmorphanrc', '.rpmorphanrc', );
+
+ foreach my $rcfile (@list_rc) {
+
+ if ( -f $rcfile ) {
+ debug("read rc from $rcfile");
+ if ( open my $fh_rc, '<', $rcfile ) {
+ read_one_rc( $rh_list, $fh_rc, $rcfile );
+ close $fh_rc
+ or warning("problem to close rc file $rcfile :$ERRNO");
+ }
+ else {
+ warning("can not open rcfile $rcfile : $ERRNO");
+ }
+ }
+ else {
+ debug("no rcfile $rcfile found");
+ }
+ }
+ return;
+}
+#########################################################
+# because arg can be given in one or several options :
+# --add toto1 --add toto2
+# --add toto1,toto2
+sub get_from_command_line(@) {
+ my @arg = @_;
+
+ my $comma = q{,};
+ ## no critic (ProhibitParensWithBuiltins);
+ return split /$comma/, join( $comma, @arg );
+ ## use critic;
+}
+#########################################################
+sub is_remove_allowed($) {
+ my $opt_dry_run = shift @_;
+
+ return ( ( $EFFECTIVE_USER_ID == 0 ) && ( !$opt_dry_run ) );
+}
+#########################################################
+# analyse command line options for guess
+sub ana_guess($) {
+ my $rh_opt = shift @_;
+
+ debug('ana_guess');
+
+ my $true = 1;
+
+ # guess-all force all others
+ if ( is_set( $rh_opt, 'guess-all' ) ) {
+ $rh_opt->{'guess-perl'} = \$true;
+ $rh_opt->{'guess-python'} = \$true;
+ $rh_opt->{'guess-pike'} = \$true;
+ $rh_opt->{'guess-ruby'} = \$true;
+ $rh_opt->{'guess-common'} = \$true;
+ $rh_opt->{'guess-data'} = \$true;
+ $rh_opt->{'guess-doc'} = \$true;
+ $rh_opt->{'guess-dev'} = \$true;
+ $rh_opt->{'guess-lib'} = \$true;
+ }
+
+ my $is_guess =
+ is_set( $rh_opt, 'guess-perl' )
+ || is_set( $rh_opt, 'guess-python' )
+ || is_set( $rh_opt, 'guess-pike' )
+ || is_set( $rh_opt, 'guess-ruby' )
+ || is_set( $rh_opt, 'guess-common' )
+ || is_set( $rh_opt, 'guess-data' )
+ || is_set( $rh_opt, 'guess-doc' )
+ || is_set( $rh_opt, 'guess-dev' )
+ || is_set( $rh_opt, 'guess-lib' )
+ || is_set( $rh_opt, 'guess-custom' );
+
+ # test if a target is set
+ if ( ( !@{ $rh_opt->{'package'} } )
+ && ( !is_set( $rh_opt, 'all' ) )
+ && ( !$is_guess ) )
+ {
+
+ # default behavior if no targets selected
+ debug('set target to guess-lib (default)');
+ $rh_opt->{'guess-lib'} = \$true;
+ }
+ return;
+}
+#########################################################
+# parse command line exclude option
+sub build_excluded($$) {
+ my $rh_opt = shift @_;
+ my $rh_excluded = shift @_;
+
+ debug('build_excluded');
+ if ( $rh_opt->{'exclude'} ) {
+
+ # allow comma separated options on tags option
+ # and build a hash for faster access
+ my @liste_ex = get_from_command_line( @{ $rh_opt->{'exclude'} } );
+ foreach my $ex (@liste_ex) {
+ $rh_excluded->{$ex} = 1;
+ debug("build_excluded : exclude $ex");
+ }
+ }
+
+ # to be passed to load_data;
+ # be carefull : we use here a new key : excluded , not exclude (command line)
+ $rh_opt->{'excluded'} = $rh_excluded;
+ return;
+}
+#########################################################
+sub get_package_info($) {
+ my $pac = shift @_;
+
+ my $cmd = "rpm -qil $pac";
+ ## no critic ( ProhibitBacktickOperators );
+ my @res = `$cmd`;
+ ## use critic
+ chomp @res;
+
+ return @res;
+}
+#########################################################
+sub get_package_summary($) {
+ my $pac = shift @_;
+
+ my $cmd = "rpm -q --queryformat '%{SUMMARY}' $pac";
+ ## no critic ( ProhibitBacktickOperators );
+ my $res = `$cmd`;
+ ## use critic
+
+ return $res;
+}
+#########################################################
+# return true is rpm allow SUGGEST tags
+sub is_suggests() {
+
+ my $cmd = 'rpm -q --querytags ';
+ ## no critic ( ProhibitBacktickOperators );
+ my @res = `$cmd`;
+ ## use critic
+ foreach my $tag (@res) {
+ chomp $tag;
+ if ( $tag =~ m/^SUGGESTS/ ) {
+ return 1;
+ }
+ }
+ return 0;
+}
+#########################################################
+
+1;
diff --git a/rpmorphan-tk-lib.pl b/rpmorphan-tk-lib.pl
new file mode 100644
index 0000000..0c0893e
--- /dev/null
+++ b/rpmorphan-tk-lib.pl
@@ -0,0 +1,441 @@
+#!/usr/bin/perl
+###############################################################################
+# rpmorphan-tk-lib.pl
+#
+# Copyright (C) 2006 by Eric Gerbier
+# Bug reports to: gerbier@users.sourceforge.net
+# $Id: rpmorphan-1.11 | rpmorphan-tk-lib.pl | Wed Jul 6 14:15:24 2011 +0000 | 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.
+#
+###############################################################################
+# this file contains all tk code for rpmorphan gui
+###############################################################################
+
+use strict;
+use warnings;
+
+use English '-no_match_vars';
+
+## no critic(ProhibitPackageVars)
+use vars
+ qw( $W_list $Opt_dry_run $Inloop @Liste_pac &display_total &insert_gui_orphans );
+## use critic
+
+################################################################
+# select all
+sub tk_select_callback() {
+
+ $W_list->selectionSet( 0, 'end' );
+ return;
+}
+################################################################
+# unselect all
+sub tk_unselect_callback() {
+
+ $W_list->selectionClear( 0, 'end' );
+ return;
+}
+#########################################################
+# change cursor to watch
+sub tk_cursor2watch() {
+
+ my $cursor = $W_list->cget('-cursor');
+ $W_list->configure( -cursor => 'watch' );
+ $W_list->update();
+
+ return $cursor;
+}
+#########################################################
+# restore cursor
+sub tk_cursor2normal($) {
+ my $cursor = shift @_; # saved cursor
+
+ # restore pointer
+ $W_list->configure( -cursor => $cursor );
+ $W_list->update();
+ return;
+}
+#########################################################
+# remove selected packages
+sub tk_remove_callback($) {
+ my $main = shift @_; # parent widget
+
+ # change pointer
+ my $cursor = tk_cursor2watch();
+
+ # get full list
+ my @liste = $W_list->get( 0, 'end' );
+
+ # get selected items
+ my @sel = $W_list->curselection();
+
+ #transform index into names
+ my @selnames = map { $liste[$_] } @sel;
+
+ # confirm dialog
+ my $answer = tk_dialog( $main, 'confirm', "confirm remove @selnames ?" );
+
+ if ( $answer =~ m/Yes/ ) {
+
+ remove_packages( \@selnames, \@liste );
+ }
+
+ # restore pointer
+ tk_cursor2normal($cursor);
+ return;
+}
+#########################################################
+# general text display in a new text window
+# is used by all help buttons
+sub tk_display_message($$@) {
+ my $main = shift @_; # parent widget
+ my $title = shift @_; # window title
+ my @baratin = @_; # text to display
+
+ my $top = $main->Toplevel( -title => $title );
+ $top->Button( -text => 'quit', -command => [ $top => 'destroy' ] )->pack();
+ my $text = $top->Scrolled(
+ 'ROText',
+ -scrollbars => 'e',
+ -height => 25,
+ -width => 128,
+ -wrap => 'word'
+ )->pack( -side => 'left', -expand => 1, -fill => 'both' );
+ $top->bind( '<Key-q>', [ $top => 'destroy' ] );
+
+ $text->insert( 'end', @baratin );
+ $text->see('1.0');
+ return;
+}
+
+################################################################
+# general dialog
+sub tk_dialog($$$) {
+ my $main = shift @_; # parent widget
+ my $title = shift @_; # window title
+ my $baratin = shift @_; # text to display
+
+ my $dialog = $main->Dialog(
+ -text => $baratin,
+ -default_button => 'Yes',
+ -buttons => [qw/Yes No/]
+ );
+ my $answer = $dialog->Show();
+
+ return $answer;
+}
+################################################################
+# display help text
+sub tk_help_callback($) {
+ my $main = shift; # parent widget
+
+ my $baratin = get_help_text();
+
+ tk_display_message( $main, 'help', $baratin );
+ return;
+}
+################################################################
+# display log file
+sub tk_log_callback($) {
+ my $main = shift; # parent widget
+
+ my @l = get_log();
+ my $l = join "\n", @l;
+
+ tk_display_message( $main, 'help', $l );
+ return;
+}
+#########################################################
+# return name of current package
+sub tk_get_current_elem() {
+
+ # get current position
+ my $elem = $W_list->get('active');
+
+ # this code will be used if we display more
+ # than name on each line
+ # my $space = q{ };
+ # my @c = split /$space/, $elem;
+ # my $c = $c[0];
+ # return $c;
+
+ return $elem;
+}
+#########################################################
+# display package summary
+sub tk_summary_callback($) {
+ my $main = shift @_; # parent widget
+
+ my $c = tk_get_current_elem();
+ if ($c) {
+ tk_popup( $main, get_package_summary($c) );
+ }
+ else {
+ print "no current element selected\n";
+ }
+
+ return;
+}
+################################################################
+# display package info
+sub tk_info_callback($) {
+ my $main = shift @_; # parent widget
+
+ # get current position
+ my $c = tk_get_current_elem();
+
+ my @res = get_package_info($c);
+ chomp @res;
+ my $message = join "\n", @res;
+
+ tk_display_message( $main, 'info', $message );
+ return;
+}
+#########################################################
+# for reload button
+sub tk_load_callback($) {
+ my $main = shift @_; # parent widget
+
+ load_data(1);
+
+ return;
+}
+#########################################################
+# is called when an item is selected/unselected
+# and change a counter
+sub tk_list_callback {
+ my $widget = shift @_;
+ my $selected = shift @_;
+
+ # get selected items
+ my @sel = $W_list->curselection();
+
+ my $nb = scalar @sel;
+
+ $selected->delete( '1.0', 'end' );
+ $selected->insert( '1.0', $nb );
+
+ return;
+}
+#########################################################
+# common sub for button and key
+sub tk_quit($) {
+ my $main = shift @_;
+ $main->destroy();
+
+ return;
+}
+#########################################################
+sub tk_popup($$) {
+ my $main = shift @_;
+ my $message = shift @_;
+
+ my $top = $main->Toplevel();
+ $top->title('popup');
+ my $d = $top->Message( -text => $message );
+ $d->pack();
+ $d->focus();
+ $top->update();
+
+ # wait 1 second and destroy popup
+ ## no critic (ProhibitMagicNumbers)
+ $main->after(1000);
+
+ $top->destroy();
+
+ return;
+}
+#########################################################
+sub build_tk_gui() {
+
+ my $main = MainWindow->new( -title => build_title() );
+ my $w_balloon = $main->Balloon();
+ my $side = 'top';
+
+ # frame for buttons
+ ###################
+ my $frame1 = $main->Frame();
+ $frame1->pack( -side => 'top', -expand => 0, -fill => 'none' );
+
+ # remove
+ my $remove_button = $frame1->Button(
+ -text => 'Remove',
+ -command => [ \&tk_remove_callback, $main ],
+ );
+ $remove_button->pack( -side => 'left' );
+ $w_balloon->attach( $remove_button, -msg => 'remove selected packages' );
+ if ( is_remove_allowed($Opt_dry_run) ) {
+
+ $main->bind( '<Key-r>', [ \&tk_remove_callback, $main ] );
+ $main->bind( '<Key-x>', [ \&tk_remove_callback, $main ] );
+ }
+ else {
+ $remove_button->configure( -state => 'disabled' );
+ }
+
+ # info
+ my $info_button = $frame1->Button(
+ -text => 'Info',
+ -command => [ \&tk_info_callback, $main ],
+ );
+ $info_button->pack( -side => 'left' );
+ $w_balloon->attach( $info_button, -msg => 'get info on current package' );
+ $main->bind( '<Key-i>', [ \&tk_info_callback, $main ] );
+
+ # select all
+ my $select_button = $frame1->Button(
+ -text => 'Select all',
+ -command => [ \&tk_select_callback ],
+ );
+ $select_button->pack( -side => 'left' );
+ $w_balloon->attach( $select_button, -msg => 'select all packages' );
+ $main->bind( '<Key-s>', [ \&tk_select_callback ] );
+
+ # unselect all
+ my $unselect_button = $frame1->Button(
+ -text => 'Unselect all',
+ -command => [ \&tk_unselect_callback ],
+ );
+ $unselect_button->pack( -side => 'left' );
+ $w_balloon->attach( $unselect_button, -msg => 'unselect all packages' );
+ $main->bind( '<Key-u>', [ \&tk_unselect_callback ] );
+
+ # log
+ my $log_button = $frame1->Button(
+ -text => 'Log',
+ -command => [ \&tk_log_callback, $main ],
+ );
+ $log_button->pack( -side => 'left' );
+ $w_balloon->attach( $log_button, -msg => 'display log' );
+ $main->bind( '<Key-l>', [ \&tk_log_callback, $main ] );
+
+ # reload button
+ my $reload_button = $frame1->Button(
+ -text => 'reLoad',
+ -command => [ \&tk_load_callback, $main ]
+ );
+ $reload_button->pack( -side => 'left' );
+ $w_balloon->attach( $reload_button, -msg => 'reload rpm database' );
+ $main->bind( '<Key-L>', [ \&tk_load_callback, $main ] );
+
+ # help
+ my $help_button = $frame1->Button(
+ -text => 'Help',
+ -command => [ \&tk_help_callback, $main ],
+ );
+ $help_button->pack( -side => 'left' );
+ $w_balloon->attach( $help_button, -msg => 'help on rpmorphan interface' );
+ $main->bind( '<Key-h>', [ \&tk_help_callback, $main ] );
+
+ # quit
+ my $exit_button =
+ $frame1->Button( -text => 'Quit', -command => [ \&tk_quit, $main ] );
+ $exit_button->pack( -side => 'left' );
+ $w_balloon->attach( $exit_button, -msg => 'quit the software' );
+ $main->bind( '<Key-q>', [ \&tk_quit, $main ] );
+
+ # frame for the list
+ ####################
+ my $frame2 = $main->Frame();
+ $frame2->pack( -side => $side, -expand => 1, -fill => 'both' );
+
+ $W_list = $frame2->Scrolled(
+ 'Listbox',
+ -scrollbars => 'e',
+ -selectmode => 'multiple'
+ )->pack( -side => $side, -expand => 1, -fill => 'both' );
+
+ $w_balloon->attach( $W_list, -msg => 'list window' );
+
+ # for summary
+ $main->bind( '<Key-S>', [ \&tk_summary_callback, $main ] );
+
+ # button 3 : full info
+ $main->bind( '<Button-3>', [ \&tk_info_callback, $main ] );
+
+ # frame for status
+ ###################
+ my $frame3 = $main->Frame();
+ $frame3->pack( -side => 'bottom', -expand => 0, -fill => 'none' );
+
+ # number of packages
+ $frame3->Label( -text => 'total' )->pack( -side => 'left' );
+ my $w_total = $frame3->ROText( -width => 4, -height => 1 );
+ $w_total->pack( -side => 'left', -expand => 0, );
+ $w_balloon->attach( $w_total, -msg => 'number of packages' );
+
+ # number of orphans
+ $frame3->Label( -text => 'orphans' )->pack( -side => 'left' );
+ my $w_orphans = $frame3->ROText( -width => 4, -height => 1 );
+ $w_orphans->pack( -side => 'left', -expand => 0, );
+ $w_balloon->attach( $w_orphans, -msg => 'number of orphans packages' );
+
+ # number of selected
+ $frame3->Label( -text => 'selected' )->pack( -side => 'left' );
+ my $w_selected = $frame3->ROText( -width => 4, -height => 1 );
+ $w_selected->pack( -side => 'left', -expand => 0, );
+ $w_balloon->attach( $w_selected, -msg => 'number of selected orphans' );
+
+ # to allow to show number of selected
+ $W_list->bind( '<<ListboxSelect>>', [ \&tk_list_callback, $w_selected ] );
+
+ # status
+ $frame3->Label( -text => 'status' )->pack( -side => 'left' );
+ my $w_status = $frame3->ROText( -width => 40, -height => 1 );
+ $w_status->pack( -side => 'left', -expand => 1, -fill => 'both' );
+ $w_balloon->attach( $w_status,
+ -msg => 'display informations on internal state' );
+
+ # for debuging
+ $main->bind( '<Key-d>', [ \&dump_struct ] );
+
+ # common gui subroutines with different coding
+ ##############################################
+ # define insert_gui_orphans as a ref to anonymous sub
+ *insert_gui_orphans = sub {
+ $W_list->delete( 0, 'end' );
+ $W_list->insert( 'end', @_ );
+
+ my $nb = scalar @_;
+ $w_orphans->delete( '1.0', 'end' );
+ $w_orphans->insert( '1.0', $nb );
+
+ # reset
+ $w_selected->delete( '1.0', 'end' );
+ $w_selected->insert( '1.0', 0 );
+ };
+
+ *display_status = sub {
+ my $text = shift @_;
+
+ debug($text);
+
+ $w_status->delete( '1.0', 'end' );
+ $w_status->insert( '1.0', $text );
+
+ # an update before the mainloop call cause a core dump
+ # so we have to test if we are before of after
+ $w_status->update() if $Inloop;
+ };
+
+ *display_total = sub {
+
+ # display orphans total
+ my $nb = scalar @Liste_pac;
+ $w_total->delete( '1.0', 'end' );
+ $w_total->insert( '1.0', $nb );
+ };
+ return;
+}
+
+#########################################################
+1;
diff --git a/rpmorphan.1 b/rpmorphan.1
new file mode 100644
index 0000000..9a08e5c
--- /dev/null
+++ b/rpmorphan.1
@@ -0,0 +1,489 @@
+.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" Escape single quotes in literal strings from groff's Unicode transform.
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.ie \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.el \{\
+. de IX
+..
+.\}
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "RPMORPHAN 1"
+.TH RPMORPHAN 1 "2012-08-01" "perl v5.14.2" "User Contributed Perl Documentation"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.if n .ad l
+.nh
+.SH "NAME"
+rpmorphan \- find orphaned packages
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+rpmorphan finds \*(L"orphaned\*(R" 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.
+.PP
+It will try to help you to remove unused packages, for exemple :
+.PP
+\&\- after a distribution upgrade
+.PP
+\&\- when you want to suppress packages after some tests
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+rpmorphan [options] [targets]
+.PP
+or
+.PP
+grpmorphan [options] [targets]
+.PP
+using grpmorphan is a short way to use \*(L"rpmorphan \-gui\*(R"
+.PP
+options:
+.PP
+.Vb 3
+\& \-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)
+.Ve
+.PP
+targets:
+.PP
+.Vb 10
+\& \-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\*(Aqs names to filter the output
+.Ve
+.PP
+keep file management
+.PP
+.Vb 5
+\& \-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
+.Ve
+.SH "REQUIRED ARGUMENTS"
+.IX Header "REQUIRED ARGUMENTS"
+you should provide at least one target. this can be
+.IP "\fB\-all\fR" 8
+.IX Item "-all"
+all the installed rpm package
+.IP "\fB\-package\fR" 8
+.IX Item "-package"
+one or several explicit package
+.IP "\fB\-guess\-*\fR" 8
+.IX Item "-guess-*"
+pre-selected packages groups
+.IP "\fB\-guess\-custom\fR" 8
+.IX Item "-guess-custom"
+give a filter to select the packages you want study. you can use regular expressions.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-help\fR" 8
+.IX Item "-help"
+Print a brief help message and exits.
+.IP "\fB\-man\fR" 8
+.IX Item "-man"
+Print the manual page and exits.
+.IP "\fB\-version\fR" 8
+.IX Item "-version"
+Print the program release and exit.
+.IP "\fB\-verbose\fR" 8
+.IX Item "-verbose"
+The program works and print debugging messages.
+.IP "\fB\-dry\-run\fR" 8
+.IX Item "-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.
+.IP "\fB\-use\-cache\fR" 8
+.IX Item "-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).
+.IP "\fB\-clear\-cache\fR" 8
+.IX Item "-clear-cache"
+to remove cache file. Can be used with \-use\-cache to write
+a new cache.
+.IP "\fB\-gui\fR" 8
+.IX Item "-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
+.IP "\fB\-tk\fR" 8
+.IX Item "-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.
+.IP "\fB\-curses\fR" 8
+.IX Item "-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.
+.IP "\fB\-exclude\fR" 8
+.IX Item "-exclude"
+this option will specify the packages to exclude from the output.
+Can be used as '\-\-exclude pac1 \-\-exclude pac2'
+or '\-\-exclude \*(L"pac1, pac2\*(R"'
+.IP "\fB\-install\-time\fR" 8
+.IX Item "-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.
+.IP "\fB\-access\-time\fR" 8
+.IX Item "-access-time"
+access-time is designed to filter packages which have not been used since x days.
+.Sp
+be careful : this option will slow the program
+.IP "\fB\-fullalgo\fR" 8
+.IX Item "-fullalgo"
+for a small list of packages, rpmorphan use a different quicker methode : rpm \-e \-\-test
+.Sp
+this option can be used to force the use of the full algo
+.IP "\fB\-suggests\fR" 8
+.IX Item "-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.
+.IP "\fB\-package\fR" 8
+.IX Item "-package"
+search if the given package(s) is(are) orphaned.
+Can be used as '\-\-package pac1 \-\-package pac2'
+or '\-\-package \*(L"pac1, pac2\*(R"'
+.IP "\fB\-all\fR" 8
+.IX Item "-all"
+apply on all installed packages. The output should be interpreted.
+For example lilo or grub are orphaned packages, but are necessary
+to boot ...
+.Sp
+the \*(L"\-install\-time\*(R" and \*(L"\-access\-time\*(R" options may be useful to filter the list
+.IP "\fB\-guess\-perl\fR" 8
+.IX Item "-guess-perl"
+This option tries to find perl modules. It tries to match \*(L"^perl\*(R"
+.IP "\fB\-guess\-python\fR" 8
+.IX Item "-guess-python"
+This option tries to find python modules. It tries to match \*(L"^python\*(R"
+.IP "\fB\-guess\-pike\fR" 8
+.IX Item "-guess-pike"
+This option tries to find pike modules. It tries to match \*(L"^pike\*(R"
+.IP "\fB\-guess\-ruby\fR" 8
+.IX Item "-guess-ruby"
+This option tries to find ruby modules. It tries to match \*(L"^ruby\*(R"
+.IP "\fB\-guess\-common\fR" 8
+.IX Item "-guess-common"
+This option tries to find common packages. It tries to match \*(L"\-common$\*(R"
+.IP "\fB\-guess\-data\fR" 8
+.IX Item "-guess-data"
+This option tries to find data packages. It tries to match \*(L"\-data$\*(R"
+.IP "\fB\-guess\-doc\fR" 8
+.IX Item "-guess-doc"
+This option tries to find documentation packages. It tries to match \*(L"\-doc$\*(R"
+.IP "\fB\-guess\-data\fR" 8
+.IX Item "-guess-data"
+This option tries to find data packages. It tries to match \*(L"\-data$\*(R"
+.IP "\fB\-guess\-dev\fR" 8
+.IX Item "-guess-dev"
+This option tries to find development packages. It tries to match \*(L"\-devel$\*(R"
+.IP "\fB\-guess\-lib\fR" 8
+.IX Item "-guess-lib"
+This option tries to find library packages. It tries to match \*(L"^lib\*(R"
+.IP "\fB\-guess\-all\fR" 8
+.IX Item "-guess-all"
+This is a short to tell : Try all of the above (perl, python ...)
+.IP "\fB\-guess\-custom\fR" 8
+.IX Item "-guess-custom"
+this will allow you to specify your own filter. for exemple \*(L"^wh\*(R"
+will match whois, whatsnewfm ...
+.IP "\fB\-keep\-file\fR" 8
+.IX Item "-keep-file"
+define the keep file to be used. If not set, the /var/lib/rpmorphan/keep will be used
+.IP "\fB\-list\-keep\fR" 8
+.IX Item "-list-keep"
+list the permanent list of excluded packages and exit.
+.IP "\fB\-zero\-keep\fR" 8
+.IX Item "-zero-keep"
+empty the permanent list of excluded packages and exit.
+.IP "\fB\-add\-keep\fR" 8
+.IX Item "-add-keep"
+add package(s) to the permanent list of excluded packages and exit.
+.Sp
+Can be used as '\-\-add\-keep pac1 \-\-add\-keep pac2'
+or '\-\-add\-keep \*(L"pac1, pac2\*(R"'
+.IP "\fB\-del\-keep\fR" 8
+.IX Item "-del-keep"
+remove package(s) from the permanent list of excluded packages and exit.
+.Sp
+Can be used as '\-\-add\-keep pac1 \-\-add\-keep pac2'
+or '\-\-add\-keep \*(L"pac1, pac2\*(R"'
+.SH "USAGE"
+.IX Header "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'.
+.PP
+If you want to remove some recent tested packages, my advice is "\-all \-install\-time \-xx'.
+.PP
+if you just want to clean your disk, use '\-all \-access\-time xxx'
+.SH "FILES"
+.IX Header "FILES"
+/var/lib/rpmorphan/keep : the permanent exclude list
+.PP
+/tmp/rpmorphan.cache : cache file to store rpm query. The cache
+file is common to all rpmorphan tools
+.PP
+/var/log/rpmorphan.log : log of all deleted packages
+.SH "CONFIGURATION"
+.IX Header "CONFIGURATION"
+the program can read rcfile if some exists.
+it will load in order
+.PP
+/etc/rpmorphanrc
+.PP
+~/.rpmorphanrc
+.PP
+\&.rpmorphanrc
+.PP
+In this file,
+.PP
+# are comments,
+.PP
+and parameters are stored in the following format :
+parameter = value
+.PP
+example :
+.PP
+all = 1
+.PP
+curses = 1
+.SH "DEPENDENCIES"
+.IX Header "DEPENDENCIES"
+rpmorphan uses standard perl module in console mode.
+.PP
+If you want to use the Tk graphical interface, you should install the Tk module
+(perl-Tk rpm package).
+.PP
+If you want to use the curses interface, you should install the Curses::UI perl module
+( perl-Curses-UI rpm package).
+.PP
+If you want to speed up rpmorphan, you should install the \s-1RPM2\s0 perl module ( perl\-RPM2 rpm package).
+On mandriva, the \s-1URPM\s0 perl module (dependency from urpmi) will be used instead.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+.IP "display orphaned libraries" 8
+.IX Item "display orphaned libraries"
+rpmorphan
+.IP "display all orphaned packages in a curses interface" 8
+.IX Item "display all orphaned packages in a curses interface"
+rpmorphan \-\-all \-\-curses
+.IP "display orphaned packages, not used since one year" 8
+.IX Item "display orphaned packages, not used since one year"
+rpmorphan \-\-all \-access\-time +365
+.IP "display all orphaned packages, installed in the last 10 days" 8
+.IX Item "display all orphaned packages, installed in the last 10 days"
+rpmorphan \-\-all \-install\-time \-10
+.IP "display all orphaned packages, installed one month ago (or more)" 8
+.IX Item "display all orphaned packages, installed one month ago (or more)"
+rpmorphan \-\-all \-install\-time +30
+.SH "BUGS AND LIMITATIONS"
+.IX Header "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.
+.PP
+the software can only work with one version of each software :
+we only treat the first version seen
+.SH "INCOMPATIBILITIES"
+.IX Header "INCOMPATIBILITIES"
+not known
+.SH "DIAGNOSTICS"
+.IX Header "DIAGNOSTICS"
+to be written
+.SH "NOTES"
+.IX Header "NOTES"
+this program can be used as \*(L"normal\*(R" user to show orphans,
+but you need to run it as supersuser (root) to remove packages
+or apply changes in the permanent exclude list
+.PP
+access-time and install-time options are \*(L"new\*(R" features, not available
+in deborphan tool
+.PP
+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.
+.PP
+For Mandriva users :
+the 'urpme \-\-auto\-orphans' command show quickly a a list of
+unused library packages.
+.SH "ENVIRONMENT"
+.IX Header "ENVIRONMENT"
+.IP "\s-1RPMORPHAN_METHOD\s0" 8
+.IX Item "RPMORPHAN_METHOD"
+for experts only : allow to force the method used to get rpm
+data. It can be set to \s-1URPM\s0, \s-1RPM2\s0 or 'basic' (for external rpm query)
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\fIrpm\fR\|(1) for rpm call
+.PP
+\fIrpmusage\fR\|(1)
+.PP
+\fIrpmdep\fR\|(1)
+.PP
+\fIrpmduplicates\fR\|(1)
+.PP
+\fIrpmextra\fR\|(1)
+.SH "EXIT STATUS"
+.IX Header "EXIT STATUS"
+should be allways 0
+.SH "LICENSE AND COPYRIGHT"
+.IX Header "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 \s-1GNU\s0 General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+.SH "AUTHOR"
+.IX Header "AUTHOR"
+Eric Gerbier
+.PP
+you can report any bug or suggest to gerbier@users.sourceforge.net
diff --git a/rpmorphan.logrotate b/rpmorphan.logrotate
new file mode 100644
index 0000000..4660ffe
--- /dev/null
+++ b/rpmorphan.logrotate
@@ -0,0 +1,7 @@
+/var/log/rpmorphan.log {
+ missingok
+ notifempty
+ size 30k
+ yearly
+ create 0600 root root
+}
diff --git a/rpmorphan.lsm b/rpmorphan.lsm
new file mode 100644
index 0000000..df87950
--- /dev/null
+++ b/rpmorphan.lsm
@@ -0,0 +1,16 @@
+Begin4
+Title: rpmorphan
+Version: 1.11
+Entered-date: 2012-08-03
+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 intends to be clone of deborphan debian tools for rpm packages.
+Keywords: administration,rpm
+Author: gerbier@users.sourceforge.net (Eric Gerbier)
+Maintained-by: gerbier@users.sourceforge.net (Eric Gerbier)
+Primary-site: http://rpmorphan.sourceforge.net
+ 46 Ko rpmorphan-1.11.tar.gz
+Platforms: linux, works with perl
+Copying-policy: GPL
+End
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</-install-time> and L</-access-time> 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
+<a href="rpmusage.1.html">rpmusage(1)</a><br />
+<a href="rpmdep.1.html">rpmdep(1)</a><br />
+<a href="rpmduplicates.1.html">rpmduplicates(1)</a><br />
+<a href="rpmextra.1.html">rpmextra(1)</a><br />
+
+=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
diff --git a/rpmorphan.spec b/rpmorphan.spec
new file mode 100644
index 0000000..d4edb60
--- /dev/null
+++ b/rpmorphan.spec
@@ -0,0 +1,271 @@
+%global _binary_filedigest_algorithm 1
+%global _source_filedigest_algorithm 1
+
+%define VERSION 1.11
+Summary: List packages that have no dependencies (like deborphan)
+# The Summary: line should be expanded to about here -----^
+Summary(fr): Liste les packages rpm orphelins (sans dependances)
+Name: rpmorphan
+Version: %{VERSION}
+Release: 1
+Group: Applications/System
+#Group(fr): (translated group goes here)
+License: GPLv2+
+
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u})
+BuildArch: noarch
+
+URL: http://rpmorphan.sourceforge.net
+Source: http://downloads.sourceforge.net/%{name}/%{name}-%{version}.tar.gz
+# Following are optional fields
+Packager: Eric Gerbier <gerbier@users.sourceforge.net>
+#Distribution: Red Hat Contrib-Net
+#Patch: src-%{version}.patch
+#Prefix: /usr
+
+Requires: perl
+Requires: rpm
+Requires: logrotate
+# rpmorphan can also use tk or curses but it is optionnal
+# so this are not required but recommanded (when rpm allow this feature)
+#Recommanded: perl-Tk
+#Recommanded: perl-Curses-UI
+# to speed up rpm query, use RPM2.pm perl package
+#Recommanded: perl-RPM2
+
+#Obsoletes:
+#BuildRequires:
+
+%description
+rpmorphan finds "orphaned"[2,5] 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 intends to be clone of
+deborphan Debian tools for rpm packages.
+
+It will try to help you to remove unused packages, for example:
+* after a distribution upgrade
+* when you want to suppress packages after some tests
+
+Several tools are also provided :
+* rpmusage - display rpm packages last use date
+* rpmdep - display the full dependency of an installed rpm package
+* rpmduplicates - find packages with several version installed
+* rpmextra - find installed packages not in distribution
+
+notes :
+
+For all users :
+---------------
+[1] this rpm package will make rpmorphan work in command line mode.
+If you want to use a graphical interface, you will need to install extra
+rpm packages : perl-Tk or perl-Curses-UI.
+
+For Fedora users :
+------------------
+[2] Note that orphan is used in the sense of Debian's deborphan, and
+is NOT the same as Fedora orphaned packages which are packages that
+have no current maintainer.
+
+[3] Yum offers a program called 'package-cleanup' which,
+called with the '--leaves' option, can show quickly a list of
+unused library packages. Rpmorphan is an extended version with
+filters, gui, etc ...
+
+[4] rpmorphan can be speed up by installing the perl-RPM2 rpm package
+
+For Mandriva users :
+--------------------
+[5] the 'urpme --auto-orphans' command show quickly a a list of
+unused library packages. Rpmorphan is an extended version with
+filters, gui, etc ...
+
+%description -l fr
+Le logiciel rpmorphan liste les packages rpm qui n'ont plus de
+dépendances avec les autres paquets installés sur votre système.
+C'est un clone du logiciel deborphan de debian pour les packages rpm.
+
+Il peut vous aider pour supprimer les packages inutilisés, par exemple :
+* après une montée de version système
+* lors de la suppression de logiciels après des tests
+
+plusieurs outils sont également fournis :
+* rpmusage : donne la date de la dernière utilisation d'un package
+* rpmdep : fournit l'ensemble des dependances (recursive) d'un package
+* rpmduplicates : cherche les packages qui ont plusieurs versions installées
+* rpmextra : cherche les packages installés hors distribution
+
+notes :
+Pour tous les utilisateurs
+--------------------------
+[1] ce package rpm permet de faire fonctionner rpmorphan en mode
+ligne de commande. Si vous voulez profiter d'une interface graphique,
+vous devez installer des packages rpm suplementaires : perl-Tk ou perl-Curses-UI
+
+Pour les utilisateurs de Fedora :
+---------------------------------
+[2] la notion d'orphelin est à prendre au sens du logiciel debian deborphan, et
+non pas au sens général de Fedora (packages sans mainteneur).
+
+[3] Yum contient le programme 'package-cleanup', qui appelé avec l'option --leaves,
+permet d'afficher rapidement les librairies inutilisées. Rpmorphan est une version
+étendue, avec notamment les filtres, l'interface graphique.
+
+[4] rpmorphan peut être accéléré en installant le package rpm perl-RPM2
+
+Pour les utilisateurs de Mandriva
+---------------------------------
+[5] la commande 'urpme --auto-orphans' permet d'afficher rapidement les librairies inutilisées.
+Rpmorphan est une version étendue, avec notamment les filtres, l'interface graphique.
+
+%prep
+%setup -q
+#%patch
+
+%build
+# dummy : nothing to build
+make %{?_smp_mflags}
+
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make DESTDIR=$RPM_BUILD_ROOT install
+
+%clean
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root,-)
+%doc rpmorphan.lsm Authors COPYING Changelog NEWS Todo Readme Readme.fr rpmorphanrc.sample
+%{_bindir}/rpmorphan-lib.pl
+%{_bindir}/rpmorphan-curses-lib.pl
+%{_bindir}/rpmorphan-tk-lib.pl
+%{_bindir}/rpmorphan.pl
+%{_bindir}/rpmorphan
+%{_bindir}/grpmorphan
+%{_bindir}/rpmusage.pl
+%{_bindir}/rpmusage
+%{_bindir}/rpmdep.pl
+%{_bindir}/rpmdep
+%{_bindir}/rpmduplicates.pl
+%{_bindir}/rpmduplicates
+%{_bindir}/rpmextra.pl
+%{_bindir}/rpmextra
+%ghost /var/log/rpmorphan.log
+%dir /var/lib/rpmorphan
+%config(noreplace) %attr(644, root, root) /var/lib/rpmorphan/keep
+%dir /usr/lib/rpmorphan
+%dir /usr/lib/rpmorphan/locale
+%dir /usr/lib/rpmorphan/locale/en
+%attr(644, root, root) /usr/lib/rpmorphan/locale/en/rpmorphan_trans.pl
+%dir /usr/lib/rpmorphan/locale/fr_FR
+%attr(644, root, root) /usr/lib/rpmorphan/locale/fr_FR/rpmorphan_trans.pl
+%config(noreplace) /etc/logrotate.d/rpmorphan
+%config(noreplace) /etc/rpmorphanrc
+%doc %{_mandir}/man1/rpmorphan.1*
+%doc %{_mandir}/man1/rpmusage.1*
+%doc %{_mandir}/man1/rpmdep.1*
+%doc %{_mandir}/man1/rpmduplicates.1*
+%doc %{_mandir}/man1/rpmextra.1*
+
+%changelog
+* Thu Aug 03 2012 Eric Gerbier <gerbier@users.sourceforge.net> 1.11
+- add suggests option
+- add /etc/rpmorphanrc
+- fix bug with space in filenames
+- add env RPMORPHAN_METHOD for debugging
+
+* Thu Jul 8 2011 Eric Gerbier <gerbier@users.sourceforge.net> 1.10
+- bugfix eval for Curses (mandriva)
+- fix bad version display
+- clean all scripts
+- reduce size/duplicated code in rpmorphan/rpmusage
+- reduce menu length in curses gui (< 72)
+- display package summary in a popup on S key
+
+* Mon Mar 21 2011 Eric Gerbier <gerbier@users.sourceforge.net> 1.9
+- fix perlcritic warnings
+- add grpmorphan as link to rpmorphan -gui
+- add rpmextra tool
+
+* Fri Apr 30 2010 <gerbier@users.sourceforge.net> 1.8-1
+- fix bug on rpmdep ( missing installtime in urpm mode)
+- add help translation (gui)
+
+* Mon Mar 1 2010 <gerbier@users.sourceforge.net> 1.7-1
+- Curses is no more an rpm dependency
+- add reload button
+
+* Thu Jan 14 2010 <gerbier@users.sourceforge.net> 1.6-1
+- curses window size is now variable to use full available size
+- split core and gui code (new rpmorphan-curses-lib.pl rpmorphan-tk-lib.pl)
+
+* Thu Nov 20 2009 <gerbier@users.sourceforge.net> 1.5-1
+- add logrotate config
+- remove all matching packages (same name)
+- fix a bug on curses interface
+- merge spec file with fedora one
+- add info for fedora/mandriva users in package description
+- french description is now in utf-8
+
+* Tue Mar 31 2009 Richard W.M. Jones <rjones@redhat.com> - 1.4-5
+- Clarify the documentation, usage of 'orphan' and other terminology.
+
+* Wed Mar 25 2009 Richard W.M. Jones <rjones@redhat.com> - 1.4-4
+- Combine all %%doc lines into one.
+- Remove Makefile from %%doc section.
+
+* Mon Jan 05 2009 <gerbier@users.sourceforge.net> 1.4-1
+- improve diagnostic if "rpm -e" return some comments
+
+* Mon Oct 19 2008 <gerbier@users.sourceforge.net> 1.3-1
+- fix a bug if exclude are set in config file (thanks Szymon Siwek)
+- display number of deleted file
+
+* Mon Apr 7 2008 <gerbier@users.sourceforge.net> 1.2-1
+- write log to /var/log/rpmorphan.log
+- add rpmdep.pl tool
+- add rpmduplicate.pl tool
+
+* Thu Nov 22 2007 <gerbier@users.sourceforge.net> 1.1-1
+- (rpmorphan) add a default target : guess-lib
+- (rpmorphan) test rpm delete
+- fix warnings from perlcritic tool
+- (rpmorphan) split too complex code (add read_one_rc, init_cache)
+- (rpmorphan) add keep-file option, which allow to cutomize the keep file
+- add rpmorphan-lib.pl to store common code for rpmorphan, rpmusage
+- (rpmusage) add a default target : all
+- use rpmlint to build a better rpm package
+
+* Wed Apr 27 2007 <gerbier@users.sourceforge.net> 1.0
+- fix bug with tk : core dump because update call before mainloop
+- add w_ prefix for widgets variables
+- add dry-run (simulation) mode
+- (gui) add 3 counters : packages, orphans, selected
+- (gui) display_status call debug sub
+- new Liste_pac global variable
+- (gui) change curses geometry to work on console
+- (gui) recursive analysis
+- improve virtuals : now check if how many package provide this one
+
+* Wed Mar 29 2007 <gerbier@users.sourceforge.net> 0.9
+- add curses interface
+- test for root access before removing packages
+- add optionnal configuration file
+- recode debugging system
+- apply conway coding rules
+- add tk_get_current_elem sub
+- add get_from_command_line sub
+- add status widget
+* Tue Mar 06 2007 <gerbier@users.sourceforge.net> 0.8
+- add simple graphical user iinterface (optionnal)
+- remove global variable opt_verbose
+- split code in functions : access_time_filter, read_rpm_data, search_orphans
+* Tue Feb 28 2007 <gerbier@users.sourceforge.net> 0.4
+- add optionnal cache
+* Tue Feb 03 2007 <gerbier@users.sourceforge.net> 0.3
+- add rpmusage tool
+- add a link from rpmorphan.pl to rpmorphan
+* Tue Jan 30 2007 <gerbier@users.sourceforge.net> 0.2
+- add permanent exclude list
+* Tue Jan 23 2007 <gerbier@users.sourceforge.net> 0.1
+- Initial spec file
diff --git a/rpmorphanrc.sample b/rpmorphanrc.sample
new file mode 100644
index 0000000..db7d300
--- /dev/null
+++ b/rpmorphanrc.sample
@@ -0,0 +1,54 @@
+# configuration file exemple for rpmorphan
+# $Id: rpmorphan-1.11 | rpmorphanrc.sample | Tue Jul 31 17:13:56 2012 +0200 | Eric Gerbier $
+# it can be used as
+# - host (general) configuration file as /etc/rpmorphanrc
+# - home (user) configuration file as ~/.rpmorphanrc
+# - local configuration file as ./.rpmorphanrc
+#
+# the program will read them all in order from host to local
+#
+# syntaxe :
+# - all line beginning with "#" are comments
+# - all parameter have the same name as command line arguments, without dash
+# - 0 means false/no, 1 means true/yes
+
+# for example, to use rpmorphan instead of rpmorphan --all --curses
+# you have to use
+# all = 1
+# curses = 1
+#
+
+## list of all available parameters with the default value
+#help = 0
+#man = 0
+#verbose = 0
+#dry-run = 0
+#version = 0
+#fullalgo = 0
+#suggests = 0
+#all = 0
+#guess-perl = 0
+#guess-python = 0
+#guess-pike = 0
+#guess-ruby = 0
+#guess-common = 0
+#guess-data = 0
+#guess-doc = 0
+#guess-dev = 0
+#guess-lib = 0
+#guess-all = 0
+#guess-custom =
+#package=
+#exclude=
+#install-time =
+#access-time =
+#add-keep =
+#del-keep =
+#list-keep =
+#zero-keep =
+#keep-file = /var/lib/rpmorphan/keep
+#use-cache = 0
+#clear-cache = 0
+#gui = 0
+#tk = 0
+#curses = 0
diff --git a/rpmusage.1 b/rpmusage.1
new file mode 100644
index 0000000..ba53b75
--- /dev/null
+++ b/rpmusage.1
@@ -0,0 +1,358 @@
+.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" Escape single quotes in literal strings from groff's Unicode transform.
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.ie \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.el \{\
+. de IX
+..
+.\}
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "RPMUSAGE 1"
+.TH RPMUSAGE 1 "2012-08-01" "perl v5.14.2" "User Contributed Perl Documentation"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.if n .ad l
+.nh
+.SH "NAME"
+rpmusage \- display rpm packages last use
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+rpmusage will display for each package, the last date it was used (in days). It can be used
+to find unused packages. It use the atime field of all package's files to do this job.
+Note : as it scan all files inodes, the run may be long ...
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+rpmusage.pl [options] [targets]
+.PP
+options:
+.PP
+.Vb 3
+\& \-help brief help message
+\& \-man full documentation
+\& \-V, \-\-version print version
+\&
+\& \-verbose verbose
+\& \-fullalgo force full algorythm
+\& \-use\-cache use cache to avoid rpm query
+\& \-clear\-cache remove cache file
+\&
+\& \-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)
+.Ve
+.PP
+targets:
+.PP
+.Vb 10
+\& \-package pac search last access on pac package
+\& \-all apply on all packages (this is the default)
+\& \-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
+\& \-guess\-all apply all \-guess\-* options (perl, python ...)
+\& \-guess\-custom regex apply the given regex to filter to package\*(Aqs names to filter the output
+.Ve
+.SH "REQUIRED ARGUMENTS"
+.IX Header "REQUIRED ARGUMENTS"
+it can be used without any argument, and then will apply on all packages
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-help\fR" 8
+.IX Item "-help"
+Print a brief help message and exits.
+.IP "\fB\-man\fR" 8
+.IX Item "-man"
+Print the manual page and exits.
+.IP "\fB\-version\fR" 8
+.IX Item "-version"
+Print the program release and exit.
+.IP "\fB\-verbose\fR" 8
+.IX Item "-verbose"
+The program works and print debugging messages.
+.IP "\fB\-use\-cache\fR" 8
+.IX Item "-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).
+.IP "\fB\-clear\-cache\fR" 8
+.IX Item "-clear-cache"
+to remove cache file. Can be used with \-use\-cache to write
+a new cache.
+.IP "\fB\-exclude\fR" 8
+.IX Item "-exclude"
+this option will specify the packages to exclude from the output.
+Can be used as '\-\-exclude pac1 \-\-exclude pac2'
+or '\-\-exclude \*(L"pac1, pac2\*(R"'
+.IP "\fB\-install\-time\fR" 8
+.IX Item "-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.
+.IP "\fB\-access\-time\fR" 8
+.IX Item "-access-time"
+access-time is designed to filter packages which have not been used since x days.
+.Sp
+be careful : this option will slow the program
+.IP "\fB\-fullalgo\fR" 8
+.IX Item "-fullalgo"
+for a small list of packages, rpmusage use a different quicker methode : rpm \-e \-\-test
+.Sp
+this option can be used to force the use of the full algo
+.IP "\fB\-package\fR" 8
+.IX Item "-package"
+search if the given package(s) is(are) orphaned.
+Can be used as '\-\-package pac1 \-\-package pac2'
+or '\-\-package \*(L"pac1, pac2\*(R"'
+.IP "\fB\-all\fR" 8
+.IX Item "-all"
+apply on all installed packages. The output should be interpreted.
+For example lilo or grub are orphaned packages, but are necessary
+to boot ...
+.Sp
+the \-install\-time and \-access\-time options may be useful to filter the list
+.IP "\fB\-guess\-perl\fR" 8
+.IX Item "-guess-perl"
+This option tries to find perl modules. It tries to match \*(L"^perl\*(R"
+.IP "\fB\-guess\-python\fR" 8
+.IX Item "-guess-python"
+This option tries to find python modules. It tries to match \*(L"^python\*(R"
+.IP "\fB\-guess\-pike\fR" 8
+.IX Item "-guess-pike"
+This option tries to find pike modules. It tries to match \*(L"^pike\*(R"
+.IP "\fB\-guess\-ruby\fR" 8
+.IX Item "-guess-ruby"
+This option tries to find ruby modules. It tries to match \*(L"^ruby\*(R"
+.IP "\fB\-guess\-common\fR" 8
+.IX Item "-guess-common"
+This option tries to find common packages. It tries to match \*(L"\-common$\*(R"
+.IP "\fB\-guess\-data\fR" 8
+.IX Item "-guess-data"
+This option tries to find data packages. It tries to match \*(L"\-data$\*(R"
+.IP "\fB\-guess\-doc\fR" 8
+.IX Item "-guess-doc"
+This option tries to find documentation packages. It tries to match \*(L"\-doc$\*(R"
+.IP "\fB\-guess\-data\fR" 8
+.IX Item "-guess-data"
+This option tries to find data packages. It tries to match \*(L"\-data$\*(R"
+.IP "\fB\-guess\-dev\fR" 8
+.IX Item "-guess-dev"
+This option tries to find development packages. It tries to match \*(L"\-devel$\*(R"
+.IP "\fB\-guess\-lib\fR" 8
+.IX Item "-guess-lib"
+This option tries to find library packages. It tries to match \*(L"^lib\*(R"
+.IP "\fB\-guess\-all\fR" 8
+.IX Item "-guess-all"
+This is a short to tell : Try all of the above (perl, python ...)
+.IP "\fB\-guess\-custom\fR" 8
+.IX Item "-guess-custom"
+this will allow you to specify your own filter. for exemple \*(L"^wh\*(R"
+will match whois, whatsnewfm ...
+.SH "USAGE"
+.IX Header "USAGE"
+display all packages in access date order
+.PP
+rpmusage.pl | sort \-k 2 \-n
+.PP
+only ask for perl packages
+.PP
+rpmusage.pl \-\-guess\-perl | sort \-k 2 \-n
+.PP
+to read the output :
+\&\*(L"python-pexpect 97 on /usr/share/doc/python\-pexpect\*(R"
+means the package was last used 97 days ago and the more recently used file was the
+/usr/share/doc/python\-pexpect file
+.SH "FILES"
+.IX Header "FILES"
+/tmp/rpmorphan.cache : cache file to store rpm query. The cache
+file is common to all rpmorphan tools
+.SH "DEPENDENCIES"
+.IX Header "DEPENDENCIES"
+rpmusage uses only standard perl module.
+.PP
+but it needs the rpm command tool.
+.SH "BUGS AND LIMITATIONS"
+.IX Header "BUGS AND LIMITATIONS"
+the software can only work with one version of each software :
+we only treat the first version seen
+.SH "CONFIGURATION"
+.IX Header "CONFIGURATION"
+the program can read rcfile if some exists.
+it will load in order
+.PP
+/etc/rpmorphanrc
+.PP
+~/.rpmorphanrc
+.PP
+\&.rpmorphanrc
+.PP
+In this file,
+.PP
+# are comments,
+.PP
+and parameters are stored in the following format :
+parameter = value
+.PP
+example :
+.PP
+all = 1
+.PP
+curses = 1
+.SH "INCOMPATIBILITIES"
+.IX Header "INCOMPATIBILITIES"
+not known
+.SH "DIAGNOSTICS"
+.IX Header "DIAGNOSTICS"
+to be written
+.SH "NOTES"
+.IX Header "NOTES"
+this program should be used as root superuser, because it needs to access
+(in read mode) to all files
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\fIrpm\fR\|(1) for rpm call
+.PP
+\fIrpmorphan\fR\|(1)
+.PP
+\fIrpmdep\fR\|(1)
+.PP
+\fIrpmduplicates\fR\|(1)
+.PP
+\fIrpmextra\fR\|(1)
+.SH "EXIT STATUS"
+.IX Header "EXIT STATUS"
+should be allways 0
+.SH "LICENSE AND COPYRIGHT"
+.IX Header "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 \s-1GNU\s0 General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+.SH "AUTHOR"
+.IX Header "AUTHOR"
+Eric Gerbier
+.PP
+you can report any bug or suggest to gerbier@users.sourceforge.net
diff --git a/rpmusage.pl b/rpmusage.pl
new file mode 100755
index 0000000..5dd71f9
--- /dev/null
+++ b/rpmusage.pl
@@ -0,0 +1,567 @@
+#!/usr/bin/perl
+###############################################################################
+# rpmusage.pl
+#
+# Copyright (C) 2006 by Eric Gerbier
+# Bug reports to: gerbier@users.sourceforge.net
+# $Id: rpmorphan-1.11 | rpmusage.pl | Tue Jul 5 11:28:04 2011 +0000 | 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 Data::Dumper; # debug
+
+# library
+use File::Basename;
+my $dirname = dirname($PROGRAM_NAME);
+require $dirname . '/rpmorphan-lib.pl'
+ or die "problem to load rpmorphan-lib: $ERRNO\n";
+
+# the code should use no "print" calls
+# but instead debug, warning, info calls
+#########################################################
+sub solve($$$$) {
+ 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
+
+ debug("solve $name");
+
+ my ( $last_time, $file ) = get_last_access( $rh_files->{$name} );
+ if ( !$file ) {
+ debug("skip $name : no files");
+
+ # can not give a time access : no files !
+ return;
+ }
+ my $diff = diff_date( $now, $last_time );
+ if ($access_time) {
+ if ( $diff > $access_time ) {
+ info("$name $diff on $file");
+ }
+ else {
+ debug("skip package $name : too recent access ($diff days)");
+ }
+ }
+ else {
+ info("$name $diff on $file");
+ }
+
+ return;
+}
+#########################################################
+sub quicksolve($$$) {
+ my $name = shift @_; # package name
+ my $access_time = shift @_; # option access-time
+ my $now = shift @_; # current time
+
+ debug("(quicksolve) : $name");
+
+ my $cmd = 'rpm -ql ';
+
+ ## no critic (ProhibitBacktickOperators);
+ my @files = `$cmd $name`;
+ ## use critic;
+ chomp @files;
+
+ # change structure to call generic call
+ my %files;
+ push @{ $files{$name} }, @files;
+
+ solve( $name, \%files, $access_time, $now );
+ return;
+}
+##########################################################
+# read all rpm database is long,
+# for a small number of packages, it is faster to use
+# another algo
+sub quick_run($$$) {
+ my $rh_opt = shift @_;
+ my $rh_excluded = shift @_;
+ my $access_time = 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
+ my $now = time;
+ PAC: foreach my $pac (@opt_package) {
+ if ( exists $rh_excluded->{$pac} ) {
+ debug("skip $pac : excluded");
+ next PAC;
+ }
+ quicksolve( $pac, $access_time, $now );
+ }
+ exit;
+ }
+ else {
+ debug('too much package for quicksolve algo');
+ }
+ }
+ else {
+ debug('full algo option');
+ }
+ return;
+}
+##########################################################
+# is to be defined because rpmorphan-lib need it
+sub display_status($) {
+ debug( shift @_ );
+ return;
+}
+#########################################################
+#
+# main
+#
+#########################################################
+my $version = '0.5';
+
+my $opt_verbose = 0;
+my $opt_help;
+my $opt_man;
+my $opt_version;
+my $opt_fullalgo;
+my $opt_use_cache;
+my $opt_clear_cache;
+
+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_access_time;
+
+my %opt = (
+ 'help' => \$opt_help,
+ 'man' => \$opt_man,
+ 'verbose' => \$opt_verbose,
+ 'version' => \$opt_version,
+ 'fullalgo' => \$opt_fullalgo,
+ 'all' => \$opt_all,
+ 'guess-perl' => \$opt_guess_perl,
+ 'guess-python' => \$opt_guess_python,
+ '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,
+ 'guess-custom' => \$opt_guess_custom,
+ 'package' => \@opt_package,
+ 'exclude' => \@opt_exclude,
+ 'install-time' => \$opt_install_time,
+ 'access-time' => \$opt_access_time,
+ 'use-cache' => \$opt_use_cache,
+ 'clear-cache' => \$opt_clear_cache,
+);
+
+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', 'use-cache!', 'clear-cache'
+) or pod2usage(2);
+
+init_debug($opt_verbose);
+
+if ($opt_help) {
+ pod2usage(1);
+}
+elsif ($opt_man) {
+ pod2usage( -verbose => 2 );
+}
+elsif ($opt_version) {
+ print_version($version);
+ exit;
+}
+
+ana_guess( \%opt );
+
+my %excluded;
+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, $opt_access_time );
+}
+
+# hash structures to be filled with rpm query
+my %files;
+my %install_time;
+
+# not really used for new
+my %provides;
+my %depends;
+my %virtual;
+my %requires;
+
+# phase 1 : get data and build structures
+read_rpm_data( \%opt, \%provides, \%install_time, \%files, \%depends, \%virtual,
+ \%requires );
+
+# phase 2 : filter
+# if a package is set, it will be first used, then it will use in order -all, then (guess*)
+# (see filter sub)
+debug('2 : filter');
+my @liste_pac;
+if (@opt_package) {
+ @liste_pac = @opt_package;
+}
+else {
+ @liste_pac = rpmfilter( \%opt, \%files );
+}
+
+# needed by access-time and install-time options
+my $now = time;
+
+# phase 3 : solve problem
+debug('3 : solve');
+PACKAGE: foreach my $pac (@liste_pac) {
+ if ( exists $excluded{$pac} ) {
+ debug("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 ) {
+ debug("skip $pac : too recent install ($diff days)");
+ next;
+ }
+ }
+ elsif ( $diff > -$opt_install_time ) {
+ debug("skip $pac : too old install ($diff days)");
+ next;
+ }
+ }
+ solve( $pac, \%files, $opt_access_time, $now );
+}
+
+__END__
+
+=head1 NAME
+
+rpmusage - display rpm packages last use
+
+=head1 DESCRIPTION
+
+rpmusage will display for each package, the last date it was used (in days). It can be used
+to find unused packages. It use the atime field of all package's files to do this job.
+Note : as it scan all files inodes, the run may be long ...
+
+=head1 SYNOPSIS
+
+rpmusage.pl [options] [targets]
+
+options:
+
+ -help brief help message
+ -man full documentation
+ -V, --version print version
+
+ -verbose verbose
+ -fullalgo force full algorythm
+ -use-cache use cache to avoid rpm query
+ -clear-cache remove cache file
+
+ -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 last access on pac package
+ -all apply on all packages (this is the default)
+ -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
+ -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
+
+=head1 REQUIRED ARGUMENTS
+
+it can be used without any argument, and then will apply on all packages
+
+=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<-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<-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, rpmusage use a different quicker methode : rpm -e --test
+
+this option can be used to force the use of the full algo
+
+=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<-install-time> and L<-access-time> 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 ...
+
+=back
+
+=head1 USAGE
+
+display all packages in access date order
+
+rpmusage.pl | sort -k 2 -n
+
+only ask for perl packages
+
+rpmusage.pl --guess-perl | sort -k 2 -n
+
+to read the output :
+"python-pexpect 97 on /usr/share/doc/python-pexpect"
+means the package was last used 97 days ago and the more recently used file was the
+/usr/share/doc/python-pexpect file
+
+=head1 FILES
+
+/tmp/rpmorphan.cache : cache file to store rpm query. The cache
+file is common to all rpmorphan tools
+
+=head1 DEPENDENCIES
+
+rpmusage uses only standard perl module.
+
+but it needs the rpm command tool.
+
+=head1 BUGS AND LIMITATIONS
+
+the software can only work with one version of each software :
+we only treat the first version seen
+
+=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 INCOMPATIBILITIES
+
+not known
+
+=head1 DIAGNOSTICS
+
+to be written
+
+=head1 NOTES
+
+this program should be used as root superuser, because it needs to access
+(in read mode) to all files
+
+=head1 SEE ALSO
+
+=for man
+\fIrpm\fR\|(1) for rpm call
+.PP
+\fIrpmorphan\fR\|(1)
+.PP
+\fIrpmdep\fR\|(1)
+.PP
+\fIrpmduplicates\fR\|(1)
+.PP
+\fIrpmextra\fR\|(1)
+
+=for html
+<a href="rpmorphan.1.html">rpmorphan(1)</a><br />
+<a href="rpmdep.1.html">rpmdep(1)</a><br />
+<a href="rpmduplicates.1.html">rpmduplicates(1)</a><br />
+<a href="rpmextra.1.html">rpmextra(1)</a><br />
+
+=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