CRUX-ARM : Home

Home :: Documentation :: Download :: Development :: Community :: Ports :: Packages :: Bugs :: Links :: About :: Donors
Initial import
authorSimone Rota <sip@crux.nu>
Fri, 11 Nov 2005 22:40:48 +0000 (23:40 +0100)
committerSimone Rota <sip@crux.nu>
Fri, 11 Nov 2005 22:40:48 +0000 (23:40 +0100)
27 files changed:
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
libtar-1.2.11-fix_linkname_overflow.patch [new file with mode: 0644]
libtar-1.2.11-fix_mem_leak.patch [new file with mode: 0644]
libtar-1.2.11-reduce_mem_usage.patch [new file with mode: 0644]
libtar-1.2.11.tar.gz [new file with mode: 0644]
main.cc [new file with mode: 0644]
pkgadd.8.in [new file with mode: 0644]
pkgadd.cc [new file with mode: 0644]
pkgadd.conf [new file with mode: 0644]
pkgadd.h [new file with mode: 0644]
pkginfo.8.in [new file with mode: 0644]
pkginfo.cc [new file with mode: 0644]
pkginfo.h [new file with mode: 0644]
pkgmk.8.in [new file with mode: 0644]
pkgmk.conf [new file with mode: 0644]
pkgmk.in [new file with mode: 0755]
pkgrm.8.in [new file with mode: 0644]
pkgrm.cc [new file with mode: 0644]
pkgrm.h [new file with mode: 0644]
pkgutil.cc [new file with mode: 0644]
pkgutil.h [new file with mode: 0644]
rejmerge.8.in [new file with mode: 0644]
rejmerge.conf [new file with mode: 0644]
rejmerge.in [new file with mode: 0755]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..96e4591
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+                   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.
+\f
+                   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.)
+\f
+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 noncommercial 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.
+\f
+  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.
+\f
+  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
+\f
+       Appendix: How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    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.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..7fffff1
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,227 @@
+5.20           - Released 2005-05-04
+               - pkgadd/rejmerge will now consider user, group and access
+                 permissions on rejected files.
+
+5.19           - Released 2005-03-29
+               - pkgadd: improved support for automatically removing
+                 rejected files that are identical to already installed files.
+               - pkgmk: added support for resuming interrupted downloads.
+                 Thanks to Johannes Winkelmann <jw@tks6.net>
+               - pkgmk: added option -cm/--check-md5sum, which checks the
+                 md5sum but does not build the package.
+               - libtar: fixed bug in symlink handling.
+                 Thanks to Guillaume Bibaut <guillaume.bibaut@free.fr>
+
+5.18           - Released 2004-05-16
+               - rejmerge: files created when merging will now get the same
+                 access permissions as the installed version.
+                 Thanks to Han Boetes <han@mijncomputer.nl>
+               - rejmerge: file diffs/merges are now piped through more(1).
+               - pkgadd/pkgrm: fixed a bug that could result in a corrupt 
+                 database when running low on disk space.
+               - pkgadd: directories can now be specified in rules in
+                 pkgadd.conf. (This fix was supposed to be part of the 5.14
+                 release, but was forgotten and actually never included).
+
+5.17           - Released 2004-05-10
+               - Fixed two bugs in rejmerge.
+
+5.16           - Released 2004-05-09
+               - pkgmk no longer redirects errors to /dev/null when removing
+                 the work dir.
+               - Minor man page updates.
+
+5.15           - Released 2004-05-02
+               - Fixed bug in makefile.
+
+5.14           - Released 2004-05-02
+               - Added new utility called rejmerge.
+                 See rejmerge(8) man page for more information.
+               - pkginfo -o now accepts regular expressions.
+               - Directories can now be specified in rules in pkgadd.conf.
+               - pkgadd/pkgrm now executes ldconfig after installing/removing
+                  a package.
+               - Minor cleanups.
+
+5.13           - Released 2003-12-16
+               - Removed "link to ..." from .footprint.
+               - pkgmk now allows the source=() array to be empty. This
+                 is useful for packages that only want create directory
+                 structures and/or symlinks.
+
+5.12           - Released 2003-11-30
+               - Added support for .nostrip, an optional file containing
+                 regular expressions matching files that should not be
+                 stripped. Thanks to Dave Hatton <mail@davehatton.it>
+
+5.11           - Released 2003-11-27
+               - Fixed bug in footprint generation.
+               - Fixed bug in file stripping.
+
+5.10           - Released 2003-11-08
+               - pkginfo: Added option -f/--footprint, which generates a
+                 package footprint. The old method for generating footprints
+                 failed in special cases.
+               - pkgmk: Updated to use pkginfo -f when creating footprints.
+               - pkgmk: Fixed bug in man page compression.
+               - pkgmk: Removed support for ROOT in Pkgfiles, use PKGMK_ROOT
+                 instead.
+               - pkgmk: Removed support for SOURCE_DIR, PACKAGE_DIR and
+                 WORK_DIR, use PKGMK_SOURCE_DIR, PKGMK_PACKAGE_DIR and
+                 PKGMK_WORK_DIR instead.
+
+5.9            - Released 2003-10-19
+               - Fixed bug in database backup code.
+               - Rejected files that are empty or equal to the already
+                 installed version are now automatically removed.
+
+5.8            - Released 2003-10-03
+               - Fixed memory leak in pkgadd.
+               - Patched libtar to fix memory leak.
+               - Patched libtar to reduce memory usage.
+               - Updated default pkgadd.conf.
+
+5.7            - Released 2003-07-31
+               - pkgmk: Reintroduced the $ROOT variable.
+
+5.6            - Released 2003-07-05
+               - pkgmk: Added automatic stripping of libraries (can be
+                 disabled with -ns/--no-strip).
+               - pkgmk: Added option -if/--ignore-footprint, which builds a
+                 package without checking the footprint.
+               - pkgmk: Synchronized names of variables exposed in pkgmk.conf
+                 to avoid potential conflicts. All variables now start with
+                 PKGMK_. The old names (SOURCE_DIR, PACKAGE_DIR and WORK_DIR)
+                 still work but this backwards compatibility will be removed
+                 in the future.
+
+5.5            - Released 2003-05-03
+               - pkgmk: Added support for alternative source, package and work
+                 directories. Variables SOURCE_DIR, PACKAGE_DIR and WORK_DIR
+                 can be set in /etc/pkgmk.conf.
+                 Thanks to Markus Ackermann <maol@symlink.ch>.
+               - Minor changes to some info/error messages.
+
+5.4            - Released 2003-03-09
+               - pkgmk: Added option -c/--clean, which removes the package
+                 and the downloaded source files.
+               - Upgraded bundled libtar from 1.2.10 to 1.2.11. This
+                 version of libtar fixes a spurious "permission denied"
+                 error, which sometimes occurred when running "pkgadd -u".
+
+5.3            - Released 2003-02-05
+               - pkgadd: the combination of -f and -u now respects the
+                 upgrade configuration in /etc/pkgadd.conf. This is
+                 needed to better support upgrades where ownership of
+                 files has been moved from one package to another.
+               - pkgadd/pkgrm/pkginfo: improved/reworked database locking
+                 and error handling.
+               - pkgmk: added -o to unzip to make it behave more like tar
+                 and avoid user intaraction when overwriting files.
+                 Thanks to Andreas Sundström <sunkan@zappa.cx>.
+               - Upgraded bundled libtar from 1.2.9 to 1.2.10.
+
+5.2            - Released 2002-12-08
+               - pkgmk: exports LC_ALL=POSIX to force utilities to use a
+                 neutral locate.
+               - Upgraded bundled libtar from 1.2.8 to 1.2.9.
+
+5.1            - Released 2002-10-27
+               - Upgraded bundled libtar from 1.2.5 to 1.2.8.
+               - pkgadd/pkgrm/pkginfo: Added file-locking on database to
+                 prevent more than one instance of pkgadd/pkgrm/pkginfo from
+                 running at the same time.
+               - pkgadd: Fixed a bug in libtar that caused segmentation fault
+                 when extracting files whose filenames contains characters
+                 with ascii value > 127.
+               - pkgmk: Fixed bug which caused suid/sgid binaries to become
+                 unstripped.
+               - pkgmk: Added option -ns/--no-strip. Use it to avoid stripping
+                 binaries in a package.
+               - pkginfo: -o/--owner does not require the whole path to the
+                 file anymore.
+
+5.0            - Released 2002-09-09
+               - Now requires GCC 3.2 to compile (due to STL incompatibility).
+               - pkginfo: -o/--owner now prepends the current directory to
+                 the file argument unless it starts with /. This feature is
+                 disable when using the -r/--root option.
+               - pkgmk: The build() function will now be aborted as soon
+                 as some command exits with an exit code other than 0 (zero).
+               - pkgmk: Binaries are now stripped automatically.
+               - pkgmk: Man pages are now compressed automatically.
+               - pkgmk: Symlinks are always given access permissions
+                 lrwxrwxrwx in .footprint, regardless of the actual
+                 access permissions. This avoids footprint problems
+                 when using e.g. XFS.
+
+4.4            - Released 2002-06-30
+               - Added option -cf, --config-file to pkgmk.
+               - Minor bugfixes.
+
+4.3            - Released 2002-06-11
+               - Removed Pkgfile.local-feature which was added in 4.2. It
+                 didn't work very well in some (common) situations.
+               - Corrected spelling errors in pkgmk.
+
+4.2            - Released 2002-05-17
+               - Added support for Pkgfile.local, which enables users to
+                 tweak packages by overriding parts of the original
+                 Pkgfile. This is useful when pkgmk is used in CRUX's
+                 ports system, where users will loose changes made to the
+                 original Pkgfile the next time they update their ports
+                 structure.
+               - Minor cleanups.
+
+4.1            - Released 2002-04-08
+               - Added support for preventing selected files (typically
+                 configuration files) from being overwritten when upgrading
+                 a package. The file /etc/pkgadd.conf, contains a list of
+                 rules with regular expressions specifying these files. These
+                 rules will be consulted when executing pkgadd with the
+                 option -u. Files that, according to the rules, shouldn't be
+                 upgraded will instead be installed under
+                 /var/lib/pkg/rejected/. The user can then examine, use and
+                 remove these files manually if so desired.
+               - Added md5sum checking (.md5sum contains the MD5 checksum of
+                 all source files). pkgmk uses this file to verify that
+                 the (potentially downloaded) source files are correct.
+               - Upgraded bundled libtar from 1.2.4 to 1.2.5.
+
+4.0.1          - Released 2002-01-20
+               - Removed warning "unable to remove XXX: Directory not empty"
+                 when upgrading a package.
+
+4.0            - Released 2002-01-14
+               - Packages are now identified by their names only (and
+                 not by name and version as before). This makes it easier
+                 for users to upgrade and remove packages. This, of course,
+                 comes with a price. You can not install two packages with
+                 the same name.
+               - The naming convention for packages is now:
+                       name#version-release.pkg.tar.gz
+                 The character '#' is not allowed in package names, since
+                 it's used as the name/version delimiter.
+               - New database layout, which gives a more robust database
+                 with a transaction-like behaviour. This implementation
+                 will gurantee that the database will never be corrupted
+                 even if the power fails when pkgadd/pkgrm is running. It
+                 does however not guarantee that the database contents is
+                 in sync with the filesystem if such a crash should occur.
+                 This means that the database will _never_ loose track of
+                 files that are installed, but it can (in case of a crash)
+                 contain files that are actually not installed. Repeating
+                 the pkgadd/pkgrm command that was running when the crash
+                 occured will get the database in sync with the filesystem
+                 again.
+               - pkgmk is now capable of downloading missing source files
+                 (using wget) before building a package (option -d), given
+                 that the URL is specified in the "source" variable.
+               - pkg.build was renamed to Pkgfile (to mimic make/Makefile).
+               - pkg.contents was renamed to .footprint.
+               - pkgmk is now capable of installing/upgrading a package if
+                 the build was successful (option -i and -u).
+               - Lot's of minor fixes and cleanups.
+
+0.1 - 3.2.0    - Released 2000-05-10 - 2001-10-03
+               (No change log was maintained during this time)
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..7517a2c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,111 @@
+#
+#  pkgutils
+#
+#  Copyright (c) 2000-2005 by Per Liden <per@fukt.bth.se>
+#
+#  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.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+#  USA.
+#
+
+DESTDIR =
+BINDIR = /usr/bin
+MANDIR = /usr/man
+ETCDIR = /etc
+
+VERSION = 5.21
+LIBTAR_VERSION = 1.2.11
+
+CXXFLAGS += -DNDEBUG
+CXXFLAGS += -O2 -Wall -pedantic -D_GNU_SOURCE -DVERSION=\"$(VERSION)\" \
+           -Ilibtar-$(LIBTAR_VERSION)/lib -Ilibtar-$(LIBTAR_VERSION)/listhash
+
+LDFLAGS += -static -Llibtar-$(LIBTAR_VERSION)/lib -ltar -lz
+
+OBJECTS = main.o pkgutil.o pkgadd.o pkgrm.o pkginfo.o
+
+MANPAGES = pkgadd.8 pkgrm.8 pkginfo.8 pkgmk.8 rejmerge.8
+
+LIBTAR = libtar-$(LIBTAR_VERSION)/lib/libtar.a
+
+all: pkgadd pkgmk rejmerge man
+
+$(LIBTAR):
+       (tar xzf libtar-$(LIBTAR_VERSION).tar.gz; \
+       cd libtar-$(LIBTAR_VERSION); \
+       patch -p1 < ../libtar-$(LIBTAR_VERSION)-fix_mem_leak.patch; \
+       patch -p1 < ../libtar-$(LIBTAR_VERSION)-reduce_mem_usage.patch; \
+       patch -p1 < ../libtar-$(LIBTAR_VERSION)-fix_linkname_overflow.patch; \
+       LDFLAGS="" ./configure --disable-encap --disable-encap-install; \
+       make)
+
+pkgadd: $(LIBTAR) .depend $(OBJECTS)
+       $(CXX) $(OBJECTS) -o $@ $(LDFLAGS)
+
+pkgmk: pkgmk.in
+
+rejmerge: rejmerge.in
+
+man: $(MANPAGES)
+
+mantxt: man $(MANPAGES:=.txt)
+
+%.8.txt: %.8
+       nroff -mandoc -c $< | col -bx > $@
+
+%: %.in
+       sed -e "s/#VERSION#/$(VERSION)/" $< > $@
+
+.depend:
+       $(CXX) $(CXXFLAGS) -MM $(OBJECTS:.o=.cc) > .depend
+
+ifeq (.depend,$(wildcard .depend))
+include .depend
+endif
+
+.PHONY:        install clean distclean dist
+
+dist: distclean
+       rm -rf /tmp/pkgutils-$(VERSION)
+       mkdir -p /tmp/pkgutils-$(VERSION)
+       cp -rf . /tmp/pkgutils-$(VERSION)
+       tar -C /tmp --exclude .svn -czvf ../pkgutils-$(VERSION).tar.gz pkgutils-$(VERSION)
+       rm -rf /tmp/pkgutils-$(VERSION)
+
+install: all
+       install -D -m0755 pkgadd $(DESTDIR)$(BINDIR)/pkgadd
+       install -D -m0644 pkgadd.conf $(DESTDIR)$(ETCDIR)/pkgadd.conf
+       install -D -m0755 pkgmk $(DESTDIR)$(BINDIR)/pkgmk
+       install -D -m0755 rejmerge $(DESTDIR)$(BINDIR)/rejmerge
+       install -D -m0644 pkgmk.conf $(DESTDIR)$(ETCDIR)/pkgmk.conf
+       install -D -m0644 rejmerge.conf $(DESTDIR)$(ETCDIR)/rejmerge.conf
+       install -D -m0644 pkgadd.8 $(DESTDIR)$(MANDIR)/man8/pkgadd.8
+       install -D -m0644 pkgrm.8 $(DESTDIR)$(MANDIR)/man8/pkgrm.8
+       install -D -m0644 pkginfo.8 $(DESTDIR)$(MANDIR)/man8/pkginfo.8
+       install -D -m0644 pkgmk.8 $(DESTDIR)$(MANDIR)/man8/pkgmk.8
+       install -D -m0644 rejmerge.8 $(DESTDIR)$(MANDIR)/man8/rejmerge.8
+       ln -sf pkgadd $(DESTDIR)$(BINDIR)/pkgrm
+       ln -sf pkgadd $(DESTDIR)$(BINDIR)/pkginfo
+
+clean:
+       rm -f .depend
+       rm -f $(OBJECTS)
+       rm -f $(MANPAGES)
+       rm -f $(MANPAGES:=.txt)
+
+distclean: clean
+       rm -f pkgadd pkginfo pkgrm pkgmk rejmerge
+       rm -rf libtar-$(LIBTAR_VERSION)
+
+# End of file
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..3fe0c15
--- /dev/null
+++ b/README
@@ -0,0 +1,28 @@
+
+              pkgutils - Package Management Utilities
+
+               http://www.fukt.bth.se/~per/pkgutils/
+
+
+Description
+-----------
+pkgutils is a set of utilities (pkgadd, pkgrm, pkginfo, pkgmk and rejmerge),
+which are used for managing software packages in Linux. It is developed for
+and used by the CRUX distribution (http://crux.nu).
+
+
+Building and installing
+-----------------------
+$ make
+$ make install
+or
+$ make DESTDIR=/some/other/path install
+
+
+Copyright
+---------
+pkgutils is Copyright (c) 2000-2005 Per Liden and is licensed through the
+GNU General Public License. Read the COPYING file for the complete license.
+
+pkgutils uses libtar, a library for reading/writing tar-files. This
+library is Copyright (c) 1998-2003 Mark D. Roth.
diff --git a/libtar-1.2.11-fix_linkname_overflow.patch b/libtar-1.2.11-fix_linkname_overflow.patch
new file mode 100644 (file)
index 0000000..7386dd2
--- /dev/null
@@ -0,0 +1,35 @@
+diff -ru libtar-1.2.11/lib/decode.c libtar-1.2.11-new/lib/decode.c
+--- libtar-1.2.11/lib/decode.c 2004-08-18 22:12:06.888107160 +0200
++++ libtar-1.2.11-new/lib/decode.c     2004-08-18 22:05:27.569812768 +0200
+@@ -42,6 +42,17 @@
+       return filename;
+ }
++char*
++th_get_linkname(TAR* t)
++{
++      static char filename[MAXPATHLEN];
++      
++      if (t->th_buf.gnu_longlink)
++              return t->th_buf.gnu_longlink;
++      
++      snprintf(filename, sizeof(filename), "%.100s", t->th_buf.linkname);
++      return filename;
++}
+ uid_t
+ th_get_uid(TAR *t)
+diff -ru libtar-1.2.11/lib/libtar.h libtar-1.2.11-new/lib/libtar.h
+--- libtar-1.2.11/lib/libtar.h 2003-01-07 02:40:59.000000000 +0100
++++ libtar-1.2.11-new/lib/libtar.h     2004-08-18 21:59:12.344855632 +0200
+@@ -184,9 +184,7 @@
+ #define th_get_mtime(t) oct_to_int((t)->th_buf.mtime)
+ #define th_get_devmajor(t) oct_to_int((t)->th_buf.devmajor)
+ #define th_get_devminor(t) oct_to_int((t)->th_buf.devminor)
+-#define th_get_linkname(t) ((t)->th_buf.gnu_longlink \
+-                            ? (t)->th_buf.gnu_longlink \
+-                            : (t)->th_buf.linkname)
++char *th_get_linkname(TAR *t);
+ char *th_get_pathname(TAR *t);
+ mode_t th_get_mode(TAR *t);
+ uid_t th_get_uid(TAR *t);
diff --git a/libtar-1.2.11-fix_mem_leak.patch b/libtar-1.2.11-fix_mem_leak.patch
new file mode 100644 (file)
index 0000000..0b09a18
--- /dev/null
@@ -0,0 +1,26 @@
+diff -ru libtar-1.2.11/lib/decode.c libtar-1.2.11-new/lib/decode.c
+--- libtar-1.2.11/lib/decode.c 2003-01-07 02:40:59.000000000 +0100
++++ libtar-1.2.11-new/lib/decode.c     2003-10-03 15:02:44.000000000 +0200
+@@ -26,7 +26,7 @@
+ char *
+ th_get_pathname(TAR *t)
+ {
+-      char filename[MAXPATHLEN];
++      static char filename[MAXPATHLEN];
+       if (t->th_buf.gnu_longname)
+               return t->th_buf.gnu_longname;
+@@ -35,11 +35,11 @@
+       {
+               snprintf(filename, sizeof(filename), "%.155s/%.100s",
+                        t->th_buf.prefix, t->th_buf.name);
+-              return strdup(filename);
++              return filename;
+       }
+       snprintf(filename, sizeof(filename), "%.100s", t->th_buf.name);
+-      return strdup(filename);
++      return filename;
+ }
diff --git a/libtar-1.2.11-reduce_mem_usage.patch b/libtar-1.2.11-reduce_mem_usage.patch
new file mode 100644 (file)
index 0000000..db28ede
--- /dev/null
@@ -0,0 +1,66 @@
+diff -ru libtar-1.2.11/lib/extract.c libtar-1.2.11-new/lib/extract.c
+--- libtar-1.2.11/lib/extract.c        2003-03-03 00:58:07.000000000 +0100
++++ libtar-1.2.11-new/lib/extract.c    2003-10-03 15:07:46.000000000 +0200
+@@ -28,14 +28,6 @@
+ #endif
+-struct linkname
+-{
+-      char ln_save[MAXPATHLEN];
+-      char ln_real[MAXPATHLEN];
+-};
+-typedef struct linkname linkname_t;
+-
+-
+ static int
+ tar_set_file_perms(TAR *t, char *realname)
+ {
+@@ -98,7 +90,9 @@
+ tar_extract_file(TAR *t, char *realname)
+ {
+       int i;
+-      linkname_t *lnp;
++      char *lnp;
++      int pathname_len;
++      int realname_len;
+       if (t->options & TAR_NOOVERWRITE)
+       {
+@@ -137,11 +131,13 @@
+       if (i != 0)
+               return i;
+-      lnp = (linkname_t *)calloc(1, sizeof(linkname_t));
++      pathname_len = strlen(th_get_pathname(t)) + 1;
++      realname_len = strlen(realname) + 1;
++      lnp = (char *)calloc(1, pathname_len + realname_len);
+       if (lnp == NULL)
+               return -1;
+-      strlcpy(lnp->ln_save, th_get_pathname(t), sizeof(lnp->ln_save));
+-      strlcpy(lnp->ln_real, realname, sizeof(lnp->ln_real));
++      strcpy(&lnp[0], th_get_pathname(t));
++      strcpy(&lnp[pathname_len], realname);
+ #ifdef DEBUG
+       printf("tar_extract_file(): calling libtar_hash_add(): key=\"%s\", "
+              "value=\"%s\"\n", th_get_pathname(t), realname);
+@@ -288,7 +284,7 @@
+ {
+       char *filename;
+       char *linktgt = NULL;
+-      linkname_t *lnp;
++      char *lnp;
+       libtar_hashptr_t hp;
+       if (!TH_ISLNK(t))
+@@ -304,8 +300,8 @@
+       if (libtar_hash_getkey(t->h, &hp, th_get_linkname(t),
+                              (libtar_matchfunc_t)libtar_str_match) != 0)
+       {
+-              lnp = (linkname_t *)libtar_hashptr_data(&hp);
+-              linktgt = lnp->ln_real;
++              lnp = (char *)libtar_hashptr_data(&hp);
++              linktgt = &lnp[strlen(lnp) + 1];
+       }
+       else
+               linktgt = th_get_linkname(t);
diff --git a/libtar-1.2.11.tar.gz b/libtar-1.2.11.tar.gz
new file mode 100644 (file)
index 0000000..ed8f796
Binary files /dev/null and b/libtar-1.2.11.tar.gz differ
diff --git a/main.cc b/main.cc
new file mode 100644 (file)
index 0000000..1681564
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,76 @@
+//
+//  pkgutils
+// 
+//  Copyright (c) 2000-2005 Per Liden
+// 
+//  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.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+//  USA.
+//
+
+#if (__GNUC__ < 3)
+#error This program requires GCC 3.x to compile.
+#endif
+
+#include <iostream>
+#include <string>
+#include <memory>
+#include <cstdlib>
+#include <libgen.h>
+#include "pkgutil.h"
+#include "pkgadd.h"
+#include "pkgrm.h"
+#include "pkginfo.h"
+
+using namespace std;
+
+static pkgutil* select_utility(const string& name)
+{
+       if (name == "pkgadd")
+               return new pkgadd;
+       else if (name == "pkgrm")
+               return new pkgrm;
+       else if (name == "pkginfo")
+               return new pkginfo;
+       else
+               throw runtime_error("command not supported by pkgutils");
+}
+
+int main(int argc, char** argv)
+{
+       string name = basename(argv[0]);
+
+       try {
+               auto_ptr<pkgutil> util(select_utility(name));
+
+               // Handle common options
+               for (int i = 1; i < argc; i++) {
+                       string option(argv[i]);
+                       if (option == "-v" || option == "--version") {
+                               util->print_version();
+                               return EXIT_SUCCESS;
+                       } else if (option == "-h" || option == "--help") {
+                               util->print_help();
+                               return EXIT_SUCCESS;
+                       }
+               }
+
+               util->run(argc, argv);
+       } catch (runtime_error& e) {
+               cerr << name << ": " << e.what() << endl;
+               return EXIT_FAILURE;
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/pkgadd.8.in b/pkgadd.8.in
new file mode 100644 (file)
index 0000000..1cfb195
--- /dev/null
@@ -0,0 +1,68 @@
+.TH pkgadd 8 "" "pkgutils #VERSION#" ""
+.SH NAME
+pkgadd \- install software package
+.SH SYNOPSIS
+\fBpkgadd [options] <file>\fP
+.SH DESCRIPTION
+\fBpkgadd\fP is a \fIpackage management\fP utility, which installs
+a software package. A \fIpackage\fP is an archive of files (.pkg.tar.gz).
+.SH OPTIONS
+.TP
+.B "\-u, \-\-upgrade"
+Upgrade/replace package with the same name as <file>.
+.TP
+.B "\-f, \-\-force"
+Force installation, overwrite conflicting files. If the package
+that is about to be installed contains files that are already
+installed this option will cause all those files to be overwritten.
+This option should be used with care, preferably not at all.
+.TP
+.B "\-r, \-\-root <path>"
+Specify alternative installation root (default is "/"). This
+should \fInot\fP be used as a way to install software into
+e.g. /usr/local instead of /usr. Instead this should be used
+if you want to install a package on a temporary mounted partition,
+which is "owned" by another system. By using this option you not only
+specify where the software should be installed, but you also
+specify which package database to use.
+.TP
+.B "\-v, \-\-version"
+Print version and exit.
+.TP
+.B "\-h, \-\-help"
+Print help and exit.
+.SH CONFIGURATION
+When using \fBpkgadd\fP in upgrade mode (i.e. option -u is used) the
+file \fI/etc/pkgadd.conf\fP will be read. This file can contain rules describing
+how pkgadd should behave when doing upgrades. A rule is built out of three
+fragments, \fIevent\fP, \fIpattern\fP and \fIaction\fP. The event describes
+in what kind of situation this rule applies. Currently only one type of event is
+supported, that is \fBUPGRADE\fP. The pattern is a regular expression and the action
+applicable to the \fBUPGRADE\fP event is \fBYES\fP and \fBNO\fP. More than one rule of the same
+event type is allowed, in which case the first rule will have the lowest priority and the last rule
+will have the highest priority. Example:
+
+.nf
+UPGRADE         ^etc/.*$                NO
+UPGRADE         ^var/log/.*$            NO
+UPGRADE         ^etc/X11/.*$            YES
+UPGRADE         ^etc/X11/XF86Config$    NO
+.fi
+
+The above example will cause pkgadd to never upgrade anything in /etc/ or /var/log/ (subdirectories included),
+except files in /etc/X11/ (subdirectories included), unless it is the file /etc/X11/XF86Config.
+The default rule is to upgrade everything, rules in this file are exceptions to that rule.
+(NOTE! A \fIpattern\fP should never contain an initial "/" since you are referring to the files in the
+package, not the files on the disk.)
+
+If pkgadd finds that a specific file should not be upgraded it will install it under \fI/var/lib/pkg/rejected/\fP.
+The user is then free to examine/use/remove that file manually.
+.SH FILES
+.TP
+.B "/etc/pkgadd.conf"
+Configuration file.
+.SH SEE ALSO
+pkgrm(8), pkginfo(8), pkgmk(8), rejmerge(8)
+.SH COPYRIGHT
+pkgadd (pkgutils) is Copyright (c) 2000-2005 Per Liden and is licensed through
+the GNU General Public License. Read the COPYING file for the complete license.
diff --git a/pkgadd.cc b/pkgadd.cc
new file mode 100644 (file)
index 0000000..dbc5641
--- /dev/null
+++ b/pkgadd.cc
@@ -0,0 +1,206 @@
+//
+//  pkgutils
+// 
+//  Copyright (c) 2000-2005 Per Liden
+// 
+//  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.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+//  USA.
+//
+
+#include "pkgadd.h"
+#include <fstream>
+#include <iterator>
+#include <cstdio>
+#include <regex.h>
+#include <unistd.h>
+
+void pkgadd::run(int argc, char** argv)
+{
+       //
+       // Check command line options
+       //
+       string o_root;
+       string o_package;
+       bool o_upgrade = false;
+       bool o_force = false;
+
+       for (int i = 1; i < argc; i++) {
+               string option(argv[i]);
+               if (option == "-r" || option == "--root") {
+                       assert_argument(argv, argc, i);
+                       o_root = argv[i + 1];
+                       i++;
+               } else if (option == "-u" || option == "--upgrade") {
+                       o_upgrade = true;
+               } else if (option == "-f" || option == "--force") {
+                       o_force = true;
+               } else if (option[0] == '-' || !o_package.empty()) {
+                       throw runtime_error("invalid option " + option);
+               } else {
+                       o_package = option;
+               }
+       }
+
+       if (o_package.empty())
+               throw runtime_error("option missing");
+
+       //
+       // Check UID
+       //
+       if (getuid())
+               throw runtime_error("only root can install/upgrade packages");
+
+       //
+       // Install/upgrade package
+       //
+       {
+               db_lock lock(o_root, true);
+               db_open(o_root);
+
+               pair<string, pkginfo_t> package = pkg_open(o_package);
+               vector<rule_t> config_rules = read_config();
+
+               bool installed = db_find_pkg(package.first);
+               if (installed && !o_upgrade)
+                       throw runtime_error("package " + package.first + " already installed (use -u to upgrade)");
+               else if (!installed && o_upgrade)
+                       throw runtime_error("package " + package.first + " not previously installed (skip -u to install)");
+      
+               set<string> conflicting_files = db_find_conflicts(package.first, package.second);
+      
+               if (!conflicting_files.empty()) {
+                       if (o_force) {
+                               set<string> keep_list;
+                               if (o_upgrade) // Don't remove files matching the rules in configuration
+                                       keep_list = make_keep_list(conflicting_files, config_rules);
+                               db_rm_files(conflicting_files, keep_list); // Remove unwanted conflicts
+                       } else {
+                               copy(conflicting_files.begin(), conflicting_files.end(), ostream_iterator<string>(cerr, "\n"));
+                               throw runtime_error("listed file(s) already installed (use -f to ignore and overwrite)");
+                       }
+               }
+   
+               set<string> keep_list;
+
+               if (o_upgrade) {
+                       keep_list = make_keep_list(package.second.files, config_rules);
+                       db_rm_pkg(package.first, keep_list);
+               }
+   
+               db_add_pkg(package.first, package.second);
+               db_commit();
+               pkg_install(o_package, keep_list);
+               ldconfig();
+       }
+}
+
+void pkgadd::print_help() const
+{
+       cout << "usage: " << utilname << " [options] <file>" << endl
+            << "options:" << endl
+            << "  -u, --upgrade       upgrade package with the same name" << endl
+            << "  -f, --force         force install, overwrite conflicting files" << endl
+            << "  -r, --root <path>   specify alternative installation root" << endl
+            << "  -v, --version       print version and exit" << endl
+            << "  -h, --help          print help and exit" << endl;
+}
+
+vector<rule_t> pkgadd::read_config() const
+{
+       vector<rule_t> rules;
+       unsigned int linecount = 0;
+       const string filename = root + PKGADD_CONF;
+       ifstream in(filename.c_str());
+
+       if (in) {
+               while (!in.eof()) {
+                       string line;
+                       getline(in, line);
+                       linecount++;
+                       if (!line.empty() && line[0] != '#') {
+                               if (line.length() >= PKGADD_CONF_MAXLINE)
+                                       throw runtime_error(filename + ":" + itos(linecount) + ": line too long, aborting");
+
+                               char event[PKGADD_CONF_MAXLINE];
+                               char pattern[PKGADD_CONF_MAXLINE];
+                               char action[PKGADD_CONF_MAXLINE];
+                               char dummy[PKGADD_CONF_MAXLINE];
+                               if (sscanf(line.c_str(), "%s %s %s %s", event, pattern, action, dummy) != 3)
+                                       throw runtime_error(filename + ":" + itos(linecount) + ": wrong number of arguments, aborting");
+
+                               if (!strcmp(event, "UPGRADE")) {
+                                       rule_t rule;
+                                       rule.event = rule_t::UPGRADE;
+                                       rule.pattern = pattern;
+                                       if (!strcmp(action, "YES")) {
+                                               rule.action = true;
+                                       } else if (!strcmp(action, "NO")) {
+                                               rule.action = false;
+                                       } else
+                                               throw runtime_error(filename + ":" + itos(linecount) + ": '" +
+                                                                   string(action) + "' unknown action, should be YES or NO, aborting");
+
+                                       rules.push_back(rule);
+                               } else
+                                       throw runtime_error(filename + ":" + itos(linecount) + ": '" +
+                                                           string(event) + "' unknown event, aborting");
+                       }
+               }
+               in.close();
+       }
+
+#ifndef NDEBUG
+       cerr << "Configuration:" << endl;
+       for (vector<rule_t>::const_iterator j = rules.begin(); j != rules.end(); j++) {
+               cerr << "\t" << (*j).pattern << "\t" << (*j).action << endl;
+       }
+       cerr << endl;
+#endif
+
+       return rules;
+}
+
+set<string> pkgadd::make_keep_list(const set<string>& files, const vector<rule_t>& rules) const
+{
+       set<string> keep_list;
+
+       for (set<string>::const_iterator i = files.begin(); i != files.end(); i++) {
+               for (vector<rule_t>::const_reverse_iterator j = rules.rbegin(); j != rules.rend(); j++) {
+                       if ((*j).event == rule_t::UPGRADE) {
+                               regex_t preg;
+                               if (regcomp(&preg, (*j).pattern.c_str(), REG_EXTENDED | REG_NOSUB))
+                                       throw runtime_error("error compiling regular expression '" + (*j).pattern + "', aborting");
+
+                               if (!regexec(&preg, (*i).c_str(), 0, 0, 0)) {
+                                       if (!(*j).action)
+                                               keep_list.insert(keep_list.end(), *i);
+                                       regfree(&preg);
+                                       break;
+                               }
+                               regfree(&preg);
+                       }
+               }
+       }
+
+#ifndef NDEBUG
+       cerr << "Keep list:" << endl;
+       for (set<string>::const_iterator j = keep_list.begin(); j != keep_list.end(); j++) {
+               cerr << "   " << (*j) << endl;
+       }
+       cerr << endl;
+#endif
+
+       return keep_list;
+}
diff --git a/pkgadd.conf b/pkgadd.conf
new file mode 100644 (file)
index 0000000..642e09b
--- /dev/null
@@ -0,0 +1,21 @@
+#
+# /etc/pkgadd.conf: pkgadd(8) configuration
+#
+
+# Default rule (implicit)
+#UPGRADE       ^.*$                    YES
+
+UPGRADE                ^etc/.*$                NO
+UPGRADE                ^var/log/.*$            NO
+
+UPGRADE                ^etc/mail/cf/.*$        YES
+UPGRADE                ^etc/ports/drivers/.*$  YES
+UPGRADE                ^etc/X11/.*$            YES
+
+UPGRADE                ^etc/rc.*$              YES
+UPGRADE                ^etc/rc\.local$         NO
+UPGRADE                ^etc/rc\.modules$       NO
+UPGRADE                ^etc/rc\.conf$          NO
+UPGRADE                ^etc/rc\.d/net$         NO
+
+# End of file
diff --git a/pkgadd.h b/pkgadd.h
new file mode 100644 (file)
index 0000000..46e0f1b
--- /dev/null
+++ b/pkgadd.h
@@ -0,0 +1,49 @@
+//
+//  pkgutils
+// 
+//  Copyright (c) 2000-2005 Per Liden
+// 
+//  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.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+//  USA.
+//
+
+#ifndef PKGADD_H
+#define PKGADD_H
+
+#include "pkgutil.h"
+#include <vector>
+#include <set>
+
+#define PKGADD_CONF             "/etc/pkgadd.conf"
+#define PKGADD_CONF_MAXLINE     1024
+
+struct rule_t {
+       enum { UPGRADE } event;
+       string pattern;
+       bool action;
+};
+
+class pkgadd : public pkgutil {
+public:
+       pkgadd() : pkgutil("pkgadd") {}
+       virtual void run(int argc, char** argv);
+       virtual void print_help() const;
+
+private:
+       vector<rule_t> read_config() const;
+       set<string> make_keep_list(const set<string>& files, const vector<rule_t>& rules) const;
+};
+
+#endif /* PKGADD_H */
diff --git a/pkginfo.8.in b/pkginfo.8.in
new file mode 100644 (file)
index 0000000..e5aba54
--- /dev/null
@@ -0,0 +1,41 @@
+.TH pkginfo 8 "" "pkgutils #VERSION#" ""
+.SH NAME
+pkginfo \- display software package information
+.SH SYNOPSIS
+\fBpkginfo [options]\fP
+.SH DESCRIPTION
+\fBpkginfo\fP is a \fIpackage management\fP utility, which displays
+information about software packages that are installed on the system
+or that reside in a particular directory.
+.SH OPTIONS
+.TP
+.B "\-i, \-\-installed"
+List installed packages and their version.
+.TP
+.B "\-l, \-\-list <package|file>"
+List files owned by the specified <package> or contained in <file>.
+.TP
+.B "\-o, \-\-owner <pattern>"
+List owner(s) of file(s) matching <pattern>.
+.TP
+.B "\-f, \-\-footprint <file>"
+Print footprint for <file>. This feature is mainly used by pkgmk(8)
+for creating and comparing footprints.
+.TP
+.B "\-r, \-\-root <path>"
+Specify alternative installation root (default is "/"). This
+should be used if you want to display information about a package
+that is installed on a temporary mounted partition, which is "owned"
+by another system. By using this option you specify which package
+database to use.
+.TP
+.B "\-v, \-\-version"
+Print version and exit.
+.TP
+.B "\-h, \-\-help"
+Print help and exit.
+.SH SEE ALSO
+pkgadd(8), pkgrm(8), pkgmk(8), rejmerge(8)
+.SH COPYRIGHT
+pkginfo (pkgutils) is Copyright (c) 2000-2005 Per Liden and is licensed through
+the GNU General Public License. Read the COPYING file for the complete license.
diff --git a/pkginfo.cc b/pkginfo.cc
new file mode 100644 (file)
index 0000000..76980a3
--- /dev/null
@@ -0,0 +1,154 @@
+//
+//  pkgutils
+// 
+//  Copyright (c) 2000-2005 Per Liden
+// 
+//  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.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+//  USA.
+//
+
+#include "pkginfo.h"
+#include <iterator>
+#include <vector>
+#include <iomanip>
+#include <sys/types.h>
+#include <regex.h>
+
+void pkginfo::run(int argc, char** argv)
+{
+       //
+       // Check command line options
+       //
+       int o_footprint_mode = 0;
+       int o_installed_mode = 0;
+       int o_list_mode = 0;
+       int o_owner_mode = 0;
+       string o_root;
+       string o_arg;
+
+       for (int i = 1; i < argc; ++i) {
+               string option(argv[i]);
+               if (option == "-r" || option == "--root") {
+                       assert_argument(argv, argc, i);
+                       o_root = argv[i + 1];
+                       i++;
+               } else if (option == "-i" || option == "--installed") {
+                       o_installed_mode += 1;
+               } else if (option == "-l" || option == "--list") {
+                       assert_argument(argv, argc, i);
+                       o_list_mode += 1;
+                       o_arg = argv[i + 1];
+                       i++;
+               } else if (option == "-o" || option == "--owner") {
+                       assert_argument(argv, argc, i);
+                       o_owner_mode += 1;
+                       o_arg = argv[i + 1];
+                       i++;
+               } else if (option == "-f" || option == "--footprint") {
+                       assert_argument(argv, argc, i);
+                       o_footprint_mode += 1;
+                       o_arg = argv[i + 1];
+                       i++;
+               } else {
+                       throw runtime_error("invalid option " + option);
+               }
+       }
+
+       if (o_footprint_mode + o_installed_mode + o_list_mode + o_owner_mode == 0)
+               throw runtime_error("option missing");
+
+       if (o_footprint_mode + o_installed_mode + o_list_mode + o_owner_mode > 1)
+               throw runtime_error("too many options");
+
+       if (o_footprint_mode) {
+               //
+               // Make footprint
+               //
+               pkg_footprint(o_arg);
+       } else {
+               //
+               // Modes that require the database to be opened
+               //
+               {
+                       db_lock lock(o_root, false);
+                       db_open(o_root);
+               }
+
+               if (o_installed_mode) {
+                       //
+                       // List installed packages
+                       //
+                       for (packages_t::const_iterator i = packages.begin(); i != packages.end(); ++i)
+                               cout << i->first << ' ' << i->second.version << endl;
+               } else if (o_list_mode) {
+                       //
+                       // List package or file contents
+                       //
+                       if (db_find_pkg(o_arg)) {
+                               copy(packages[o_arg].files.begin(), packages[o_arg].files.end(), ostream_iterator<string>(cout, "\n"));
+                       } else if (file_exists(o_arg)) {
+                               pair<string, pkginfo_t> package = pkg_open(o_arg);
+                               copy(package.second.files.begin(), package.second.files.end(), ostream_iterator<string>(cout, "\n"));
+                       } else {
+                               throw runtime_error(o_arg + " is neither an installed package nor a package file");
+                       }
+               } else {
+                       //
+                       // List owner(s) of file or directory
+                       //
+                       regex_t preg;
+                       if (regcomp(&preg, o_arg.c_str(), REG_EXTENDED | REG_NOSUB))
+                               throw runtime_error("error compiling regular expression '" + o_arg + "', aborting");
+
+                       vector<pair<string, string> > result;
+                       result.push_back(pair<string, string>("Package", "File"));
+                       unsigned int width = result.begin()->first.length(); // Width of "Package"
+                       
+                       for (packages_t::const_iterator i = packages.begin(); i != packages.end(); ++i) {
+                               for (set<string>::const_iterator j = i->second.files.begin(); j != i->second.files.end(); ++j) {
+                                       const string file('/' + *j);
+                                       if (!regexec(&preg, file.c_str(), 0, 0, 0)) {
+                                               result.push_back(pair<string, string>(i->first, *j));
+                                               if (i->first.length() > width)
+                                                       width = i->first.length();
+                                       }
+                               }
+                       }
+                       
+                       regfree(&preg);
+                       
+                       if (result.size() > 1) {
+                               for (vector<pair<string, string> >::const_iterator i = result.begin(); i != result.end(); ++i) {
+                                       cout << left << setw(width + 2) << i->first << i->second << endl;
+                               }
+                       } else {
+                               cout << utilname << ": no owner(s) found" << endl;
+                       }
+               }
+       }
+}
+
+void pkginfo::print_help() const
+{
+       cout << "usage: " << utilname << " [options]" << endl
+            << "options:" << endl
+            << "  -i, --installed             list installed packages" << endl
+            << "  -l, --list <package|file>   list files in <package> or <file>" << endl
+            << "  -o, --owner <pattern>       list owner(s) of file(s) matching <pattern>" << endl
+            << "  -f, --footprint <file>      print footprint for <file>" << endl
+            << "  -r, --root <path>           specify alternative installation root" << endl
+            << "  -v, --version               print version and exit" << endl
+            << "  -h, --help                  print help and exit" << endl;
+}
diff --git a/pkginfo.h b/pkginfo.h
new file mode 100644 (file)
index 0000000..25a4686
--- /dev/null
+++ b/pkginfo.h
@@ -0,0 +1,34 @@
+//
+//  pkgutils
+// 
+//  Copyright (c) 2000-2005 Per Liden
+// 
+//  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.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+//  USA.
+//
+
+#ifndef PKGINFO_H
+#define PKGINFO_H
+
+#include "pkgutil.h"
+
+class pkginfo : public pkgutil {
+public:
+       pkginfo() : pkgutil("pkginfo") {}
+       virtual void run(int argc, char** argv);
+       virtual void print_help() const;
+};
+
+#endif /* PKGINFO_H */
diff --git a/pkgmk.8.in b/pkgmk.8.in
new file mode 100644 (file)
index 0000000..0be8d3a
--- /dev/null
@@ -0,0 +1,92 @@
+.TH pkgmk 8 "" "pkgutils #VERSION#" ""
+.SH NAME
+pkgmk \- make software package
+.SH SYNOPSIS
+\fBpkgmk [options]\fP
+.SH DESCRIPTION
+\fBpkgmk\fP is a \fIpackage management\fP utility, which makes
+a software package. A \fIpackage\fP is an archive of files (.pkg.tar.gz)
+that can be installed using pkgadd(8).
+
+To prepare to use pkgmk, you must write a file named \fIPkgfile\fP
+that describes how the package should be build. Once a suitable
+\fIPkgfile\fP file exists, each time you change some source files,
+you simply execute pkgmk to bring the package up to date. The pkgmk
+program uses the \fIPkgfile\fP file and the last-modification
+times of the source files to decide if the package needs to be updated.
+
+Global build configuration is stored in \fI/etc/pkgmk.conf\fP. This
+file is read by pkgmk at startup.
+.SH OPTIONS
+.TP
+.B "\-i, \-\-install"
+Install package using pkgadd(8) after successful build.
+.TP
+.B "\-u, \-\-upgrade"
+Install package as an upgrade using pkgadd(8) after successful build.
+.TP
+.B "\-r, \-\-recursive"
+Search for and build packages recursively.
+.TP
+.B "\-d, \-\-download"
+Download missing source file(s).
+.TP
+.B "\-do, \-\-download\-only"
+Do not build, only download missing source file(s).
+.TP
+.B "\-utd, \-\-up\-to\-date"
+Do not build, only check if the package is up to date.
+.TP
+.B "\-uf, \-\-update\-footprint"
+Update footprint and treat last build as successful.
+.TP
+.B "\-if, \-\-ignore\-footprint"
+Build package without checking footprint.
+.TP
+.B "\-um, \-\-update\-md5sum"
+Update md5sum using the current source files.
+.TP
+.B "\-im, \-\-ignore\-md5sum"
+Build package without checking md5sum first.
+.TP
+.B "\-ns, \-\-no\-strip"
+Do not strip executable binaries or libraries.
+.TP
+.B "\-f, \-\-force"
+Build package even if it appears to be up to date.
+.TP
+.B "\-c, \-\-clean"
+Remove the (previously built) package and the downloaded source files.
+.TP
+.B "\-kw, \-\-keep-work"
+Keep temporary working directory.
+.TP
+.B "\-cf, \-\-config\-file <file>"
+Use alternative configuration file (default is /etc/pkgmk.conf).
+.TP
+.B "\-v, \-\-version"
+Print version and exit.
+.TP
+.B "\-h, \-\-help"
+Print help and exit.
+.SH FILES
+.TP
+.B "Pkgfile"
+Package build description.
+.TP
+.B ".footprint"
+Package footprint (used for regression testing).
+.TP
+.B ".md5sum"
+MD5 checksum of source files.
+.TP
+.B "/etc/pkgmk.conf"
+Global package make configuration.
+.TP
+.B "wget"
+Used by pkgmk to download source code.
+.SH SEE ALSO
+pkgadd(8), pkgrm(8), pkginfo(8), rejmerge(8), wget(1)
+.SH COPYRIGHT
+pkgmk (pkgutils) is Copyright (c) 2000-2005 Per Liden and is licensed through
+the GNU General Public License. Read the COPYING file for the complete license.
diff --git a/pkgmk.conf b/pkgmk.conf
new file mode 100644 (file)
index 0000000..6e70f1b
--- /dev/null
@@ -0,0 +1,15 @@
+#
+# /etc/pkgmk.conf: pkgmk(8) configuration
+#
+
+export CFLAGS="-O2 -march=i686 -pipe"
+export CXXFLAGS="-O2 -march=i686 -pipe"
+
+# PKGMK_SOURCE_DIR="$PWD"
+# PKGMK_PACKAGE_DIR="$PWD"
+# PKGMK_WORK_DIR="$PWD/work"
+# PKGMK_DOWNLOAD="no"
+# PKGMK_IGNORE_FOOTPRINT="no"
+# PKGMK_NO_STRIP="no"
+
+# End of file
diff --git a/pkgmk.in b/pkgmk.in
new file mode 100755 (executable)
index 0000000..62a7418
--- /dev/null
+++ b/pkgmk.in
@@ -0,0 +1,653 @@
+#!/bin/bash
+#
+#  pkgutils
+# 
+#  Copyright (c) 2000-2005 Per Liden
+# 
+#  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.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+#  USA.
+#
+
+info() {
+       echo "=======> $1"
+}
+
+warning() {
+       info "WARNING: $1"
+}
+
+error() {
+       info "ERROR: $1"
+}
+
+get_filename() {
+       local FILE="`echo $1 | sed 's|^.*://.*/||g'`"
+
+       if [ "$FILE" != "$1" ]; then
+               FILE="$PKGMK_SOURCE_DIR/$FILE"
+       fi
+
+       echo $FILE
+}
+
+check_pkgfile() {
+       if [ ! "$name" ]; then
+               error "Variable 'name' not specified in $PKGMK_PKGFILE."
+               exit 1
+       elif [ ! "$version" ]; then
+               error "Variable 'version' not specified in $PKGMK_PKGFILE."
+               exit 1
+       elif [ ! "$release" ]; then
+               error "Variable 'release' not specified in $PKGMK_PKGFILE."
+               exit 1
+       elif [ "`type -t build`" != "function" ]; then
+               error "Function 'build' not specified in $PKGMK_PKGFILE."
+               exit 1
+       fi
+}
+
+check_directory() {
+       if [ ! -d $1 ]; then
+               error "Directory '$1' does not exist."
+               exit 1
+       elif [ ! -w $1 ]; then
+               error "Directory '$1' not writable."
+               exit 1
+       elif [ ! -x $1 ] || [ ! -r $1 ]; then
+               error "Directory '$1' not readable."
+               exit 1
+       fi
+}
+
+download_file() {
+       info "Downloading '$1'."
+
+       if [ ! "`type -p wget`" ]; then
+               error "Command 'wget' not found."
+               exit 1
+       fi
+
+       LOCAL_FILENAME=`get_filename $1`
+       LOCAL_FILENAME_PARTIAL="$LOCAL_FILENAME.partial"
+       DOWNLOAD_CMD="--passive-ftp --no-directories --tries=3 --waitretry=3 \
+                     --directory-prefix=$PKGMK_SOURCE_DIR --output-document=$LOCAL_FILENAME_PARTIAL $1"
+
+       if [ -f "$LOCAL_FILENAME_PARTIAL" ]; then
+               info "Partial download found, trying to resume"
+               RESUME_CMD="-c"
+       fi
+
+       while true; do
+               wget $RESUME_CMD $DOWNLOAD_CMD
+               error=$?
+               if [ $error != 0 ] && [ "$RESUME_CMD" ]; then
+                       info "Partial download failed, restarting"
+                       rm -f "$LOCAL_FILENAME_PARTIAL"
+                       RESUME_CMD=""
+               else
+                       break
+               fi
+       done
+       
+       if [ $error != 0 ]; then
+               error "Downloading '$1' failed."
+               exit 1
+       fi
+       
+       mv -f "$LOCAL_FILENAME_PARTIAL" "$LOCAL_FILENAME"
+}
+
+download_source() {
+       local FILE LOCAL_FILENAME
+
+       for FILE in ${source[@]}; do
+               LOCAL_FILENAME=`get_filename $FILE`
+               if [ ! -e $LOCAL_FILENAME ]; then
+                       if [ "$LOCAL_FILENAME" = "$FILE" ]; then
+                               error "Source file '$LOCAL_FILENAME' not found (can not be downloaded, URL not specified)."
+                               exit 1
+                       else
+                               if [ "$PKGMK_DOWNLOAD" = "yes" ]; then
+                                       download_file $FILE
+                               else
+                                       error "Source file '$LOCAL_FILENAME' not found (use option -d to download)."
+                                       exit 1
+                               fi
+                       fi
+               fi
+       done
+}
+
+unpack_source() {
+       local FILE LOCAL_FILENAME COMMAND
+       
+       for FILE in ${source[@]}; do
+               LOCAL_FILENAME=`get_filename $FILE`
+               case $LOCAL_FILENAME in
+                       *.tar.gz|*.tar.Z|*.tgz)
+                               COMMAND="tar -C $SRC --use-compress-program=gzip -xf $LOCAL_FILENAME" ;;
+                       *.tar.bz2)
+                               COMMAND="tar -C $SRC --use-compress-program=bzip2 -xf $LOCAL_FILENAME" ;;
+                       *.zip)
+                               COMMAND="unzip -qq -o -d $SRC $LOCAL_FILENAME" ;;
+                       *)
+                               COMMAND="cp $LOCAL_FILENAME $SRC" ;;
+               esac
+
+               echo "$COMMAND"
+
+               $COMMAND
+
+               if [ $? != 0 ]; then
+                       if [ "$PKGMK_KEEP_WORK" = "no" ]; then
+                               rm -rf $PKGMK_WORK_DIR
+                       fi
+                       error "Building '$TARGET' failed."
+                       exit 1
+               fi
+       done
+}
+
+make_md5sum() {
+       local FILE LOCAL_FILENAMES
+       
+       if [ "$source" ]; then
+               for FILE in ${source[@]}; do
+                       LOCAL_FILENAMES="$LOCAL_FILENAMES `get_filename $FILE`"
+               done
+               
+               md5sum $LOCAL_FILENAMES | sed -e 's|  .*/|  |' | sort -k 2
+       fi
+}
+
+make_footprint() {
+       pkginfo --footprint $TARGET | \
+               sed "s|\tlib/modules/`uname -r`/|\tlib/modules/<kernel-version>/|g" | \
+               sort -k 3
+}
+
+check_md5sum() {
+       local FILE="$PKGMK_WORK_DIR/.tmp"
+
+       cd $PKGMK_ROOT
+       
+       if [ -f $PKGMK_MD5SUM ]; then
+               make_md5sum > $FILE.md5sum
+               sort -k 2 $PKGMK_MD5SUM > $FILE.md5sum.orig
+               diff -w -t -U 0 $FILE.md5sum.orig $FILE.md5sum | \
+                       sed '/^@@/d' | \
+                       sed '/^+++/d' | \
+                       sed '/^---/d' | \
+                       sed 's/^+/NEW       /g' | \
+                       sed 's/^-/MISSING   /g' > $FILE.md5sum.diff
+               if [ -s $FILE.md5sum.diff ]; then
+                       error "Md5sum mismatch found:"
+                       cat $FILE.md5sum.diff
+
+                       if [ "$PKGMK_KEEP_WORK" = "no" ]; then
+                               rm -rf $PKGMK_WORK_DIR
+                       fi
+
+                       if [ "$PKGMK_CHECK_MD5SUM" = "yes" ]; then
+                               error "Md5sum not ok."
+                               exit 1
+                       fi
+
+                       error "Building '$TARGET' failed."
+                       exit 1
+               fi
+       else
+               if [ "$PKGMK_CHECK_MD5SUM" = "yes" ]; then
+                       if [ "$PKGMK_KEEP_WORK" = "no" ]; then
+                               rm -rf $PKGMK_WORK_DIR
+                       fi
+                       info "Md5sum not found."
+                       exit 1
+               fi
+               
+               warning "Md5sum not found, creating new."
+               make_md5sum > $PKGMK_MD5SUM
+       fi
+
+       if [ "$PKGMK_CHECK_MD5SUM" = "yes" ]; then
+               if [ "$PKGMK_KEEP_WORK" = "no" ]; then
+                       rm -rf $PKGMK_WORK_DIR
+               fi
+               info "Md5sum ok."
+               exit 0
+       fi
+}
+
+strip_files() {
+       local FILE FILTER
+       
+       cd $PKG
+       
+       if [ -f $PKGMK_ROOT/$PKGMK_NOSTRIP ]; then
+               FILTER="grep -v -f $PKGMK_ROOT/$PKGMK_NOSTRIP"
+       else
+               FILTER="cat"
+       fi
+
+       find . -type f -printf "%P\n" | $FILTER | while read FILE; do
+               if file -b "$FILE" | grep '^.*ELF.*executable.*not stripped$' &> /dev/null; then
+                       strip --strip-all "$FILE"
+               elif file -b "$FILE" | grep '^.*ELF.*shared object.*not stripped$' &> /dev/null; then
+                       strip --strip-unneeded "$FILE"
+               elif file -b "$FILE" | grep '^current ar archive$' &> /dev/null; then
+                       strip --strip-debug "$FILE"
+               fi
+       done
+}
+
+compress_manpages() {
+       local FILE DIR TARGET
+
+       cd $PKG
+       
+       find . -type f -path "*/man/man*/*" | while read FILE; do
+               if [ "$FILE" = "${FILE%%.gz}" ]; then
+                       gzip -9 "$FILE"
+               fi
+       done
+       
+       find . -type l -path "*/man/man*/*" | while read FILE; do
+               TARGET=`readlink -n "$FILE"`
+               TARGET="${TARGET##*/}"
+               TARGET="${TARGET%%.gz}.gz"
+               rm -f "$FILE"
+               FILE="${FILE%%.gz}.gz"
+               DIR=`dirname "$FILE"`
+
+               if [ -e "$DIR/$TARGET" ]; then
+                       ln -sf "$TARGET" "$FILE"
+               fi
+       done
+}
+
+check_footprint() {
+       local FILE="$PKGMK_WORK_DIR/.tmp"
+       
+       cd $PKGMK_ROOT
+       
+       if [ -f $TARGET ]; then
+               make_footprint > $FILE.footprint
+               if [ -f $PKGMK_FOOTPRINT ]; then
+                       sort -k 3 $PKGMK_FOOTPRINT > $FILE.footprint.orig
+                       diff -w -t -U 0 $FILE.footprint.orig $FILE.footprint | \
+                               sed '/^@@/d' | \
+                               sed '/^+++/d' | \
+                               sed '/^---/d' | \
+                               sed 's/^+/NEW       /g' | \
+                               sed 's/^-/MISSING   /g' > $FILE.footprint.diff
+                       if [ -s $FILE.footprint.diff ]; then
+                               error "Footprint mismatch found:"
+                               cat $FILE.footprint.diff
+                               BUILD_SUCCESSFUL="no"
+                       fi
+               else
+                       warning "Footprint not found, creating new."
+                       mv $FILE.footprint $PKGMK_FOOTPRINT
+               fi
+       else
+               error "Package '$TARGET' was not found."
+               BUILD_SUCCESSFUL="no"
+       fi
+}
+
+build_package() {
+       local BUILD_SUCCESSFUL="no"
+       
+       export PKG="$PKGMK_WORK_DIR/pkg"
+       export SRC="$PKGMK_WORK_DIR/src"
+       umask 022
+       
+       cd $PKGMK_ROOT
+       rm -rf $PKGMK_WORK_DIR
+       mkdir -p $SRC $PKG
+       
+       if [ "$PKGMK_IGNORE_MD5SUM" = "no" ]; then
+               check_md5sum
+       fi
+
+       if [ "$UID" != "0" ]; then
+               warning "Packages should be built as root."
+       fi
+       
+       info "Building '$TARGET'."
+       
+       unpack_source
+       
+       cd $SRC
+       (set -e -x ; build)
+       
+       if [ $? = 0 ]; then
+               if [ "$PKGMK_NO_STRIP" = "no" ]; then
+                       strip_files
+               fi
+               
+               compress_manpages
+               
+               cd $PKG
+               info "Build result:"
+               tar czvvf $TARGET *
+               
+               if [ $? = 0 ]; then
+                       BUILD_SUCCESSFUL="yes"
+
+                       if [ "$PKGMK_IGNORE_FOOTPRINT" = "yes" ]; then
+                               warning "Footprint ignored."
+                       else
+                               check_footprint
+                       fi
+               fi
+       fi
+       
+       if [ "$PKGMK_KEEP_WORK" = "no" ]; then
+               rm -rf $PKGMK_WORK_DIR
+       fi
+       
+       if [ "$BUILD_SUCCESSFUL" = "yes" ]; then
+               info "Building '$TARGET' succeeded."
+       else
+               if [ -f $TARGET ]; then
+                       touch -r $PKGMK_ROOT/$PKGMK_PKGFILE $TARGET &> /dev/null
+               fi
+               error "Building '$TARGET' failed."
+               exit 1
+       fi
+}
+
+install_package() {
+       local COMMAND
+       
+       info "Installing '$TARGET'."
+       
+       if [ "$PKGMK_INSTALL" = "install" ]; then
+               COMMAND="pkgadd $TARGET"
+       else
+               COMMAND="pkgadd -u $TARGET"
+       fi
+       
+       cd $PKGMK_ROOT
+       echo "$COMMAND"
+       $COMMAND
+       
+       if [ $? = 0 ]; then
+               info "Installing '$TARGET' succeeded."
+       else
+               error "Installing '$TARGET' failed."
+               exit 1
+       fi
+}
+
+recursive() {
+       local ARGS FILE DIR
+       
+       ARGS=`echo "$@" | sed -e "s/--recursive//g" -e "s/-r//g"`
+       
+       for FILE in `find $PKGMK_ROOT -name $PKGMK_PKGFILE | sort`; do
+               DIR="`dirname $FILE`/"
+               if [ -d $DIR ]; then
+                       info "Entering directory '$DIR'."
+                       (cd $DIR && $PKGMK_COMMAND $ARGS)
+                       info "Leaving directory '$DIR'."
+               fi
+       done
+}
+
+clean() {
+       local FILE LOCAL_FILENAME
+       
+       if [ -f $TARGET ]; then
+               info "Removing $TARGET"
+               rm -f $TARGET
+       fi
+       
+       for FILE in ${source[@]}; do
+               LOCAL_FILENAME=`get_filename $FILE`
+               if [ -e $LOCAL_FILENAME ] && [ "$LOCAL_FILENAME" != "$FILE" ]; then
+                       info "Removing $LOCAL_FILENAME"
+                       rm -f $LOCAL_FILENAME
+               fi
+       done
+}
+
+update_footprint() {
+       if [ ! -f $TARGET ]; then
+               error "Unable to update footprint. File '$TARGET' not found."
+               exit 1
+       fi
+       
+       make_footprint > $PKGMK_FOOTPRINT
+       touch $TARGET
+       
+       info "Footprint updated."
+}
+
+build_needed() {
+       local FILE RESULT
+       
+       RESULT="yes"
+       if [ -f $TARGET ]; then
+               RESULT="no"
+               for FILE in $PKGMK_PKGFILE ${source[@]}; do
+                       FILE=`get_filename $FILE`
+                       if [ ! -e $FILE ] || [ ! $TARGET -nt $FILE ]; then
+                               RESULT="yes"
+                               break
+                       fi
+               done
+       fi
+       
+       echo $RESULT
+}
+
+interrupted() {
+       echo ""
+       error "Interrupted."
+       
+       if [ "$PKGMK_KEEP_WORK" = "no" ]; then
+               rm -rf $PKGMK_WORK_DIR
+       fi
+       
+       exit 1
+}
+
+print_help() {
+       echo "usage: `basename $PKGMK_COMMAND` [options]"
+       echo "options:"
+       echo "  -i,   --install             build and install package"
+       echo "  -u,   --upgrade             build and install package (as upgrade)"
+       echo "  -r,   --recursive           search for and build packages recursively"
+       echo "  -d,   --download            download missing source file(s)"
+       echo "  -do,  --download-only       do not build, only download missing source file(s)"
+       echo "  -utd, --up-to-date          do not build, only check if package is up to date"
+       echo "  -uf,  --update-footprint    update footprint using result from last build"
+       echo "  -if,  --ignore-footprint    build package without checking footprint"
+       echo "  -um,  --update-md5sum       update md5sum"
+       echo "  -im,  --ignore-md5sum       build package without checking md5sum"
+       echo "  -cm,  --check-md5sum        do not build, only check md5sum"
+       echo "  -ns,  --no-strip            do not strip executable binaries or libraries"
+       echo "  -f,   --force               build package even if it appears to be up to date"
+       echo "  -c,   --clean               remove package and downloaded files"
+       echo "  -kw,  --keep-work           keep temporary working directory"
+       echo "  -cf,  --config-file <file>  use alternative configuration file"
+       echo "  -v,   --version             print version and exit "
+       echo "  -h,   --help                print help and exit"
+}
+
+parse_options() {
+       while [ "$1" ]; do
+               case $1 in
+                       -i|--install)
+                               PKGMK_INSTALL="install" ;;
+                       -u|--upgrade)
+                               PKGMK_INSTALL="upgrade" ;;
+                       -r|--recursive)
+                               PKGMK_RECURSIVE="yes" ;;
+                       -d|--download)
+                               PKGMK_DOWNLOAD="yes" ;;
+                       -do|--download-only)
+                               PKGMK_DOWNLOAD="yes"
+                               PKGMK_DOWNLOAD_ONLY="yes" ;;
+                       -utd|--up-to-date)
+                               PKGMK_UP_TO_DATE="yes" ;;
+                       -uf|--update-footprint)
+                               PKGMK_UPDATE_FOOTPRINT="yes" ;;
+                       -if|--ignore-footprint)
+                               PKGMK_IGNORE_FOOTPRINT="yes" ;;
+                       -um|--update-md5sum)
+                               PKGMK_UPDATE_MD5SUM="yes" ;;
+                       -im|--ignore-md5sum)
+                               PKGMK_IGNORE_MD5SUM="yes" ;;
+                       -cm|--check-md5sum)
+                               PKGMK_CHECK_MD5SUM="yes" ;;
+                       -ns|--no-strip)
+                               PKGMK_NO_STRIP="yes" ;;
+                       -f|--force)
+                               PKGMK_FORCE="yes" ;;
+                       -c|--clean)
+                               PKGMK_CLEAN="yes" ;;
+                       -kw|--keep-work)
+                               PKGMK_KEEP_WORK="yes" ;;
+                       -cf|--config-file)
+                               if [ ! "$2" ]; then
+                                       echo "`basename $PKGMK_COMMAND`: option $1 requires an argument"
+                                       exit 1
+                               fi
+                               PKGMK_CONFFILE="$2"
+                               shift ;;
+                       -v|--version)
+                               echo "`basename $PKGMK_COMMAND` (pkgutils) $PKGMK_VERSION"
+                               exit 0 ;;
+                       -h|--help)
+                               print_help
+                               exit 0 ;;
+                       *)
+                               echo "`basename $PKGMK_COMMAND`: invalid option $1"
+                               exit 1 ;;
+               esac
+               shift
+       done
+}
+
+main() {
+       local FILE TARGET
+       
+       parse_options "$@"
+       
+       if [ "$PKGMK_RECURSIVE" = "yes" ]; then
+               recursive "$@"
+               exit 0
+       fi
+       
+       for FILE in $PKGMK_PKGFILE $PKGMK_CONFFILE; do
+               if [ ! -f $FILE ]; then
+                       error "File '$FILE' not found."
+                       exit 1
+               fi
+               . $FILE
+       done
+       
+       check_directory "$PKGMK_SOURCE_DIR"
+       check_directory "$PKGMK_PACKAGE_DIR"
+       check_directory "`dirname $PKGMK_WORK_DIR`"
+       
+       check_pkgfile
+       
+       TARGET="$PKGMK_PACKAGE_DIR/$name#$version-$release.pkg.tar.gz"
+       
+       if [ "$PKGMK_CLEAN" = "yes" ]; then
+               clean
+               exit 0
+       fi
+       
+       if [ "$PKGMK_UPDATE_FOOTPRINT" = "yes" ]; then
+               update_footprint
+               exit 0
+       fi
+       
+       if [ "$PKGMK_UPDATE_MD5SUM" = "yes" ]; then
+               download_source
+               make_md5sum > $PKGMK_MD5SUM
+               info "Md5sum updated."
+               exit 0
+       fi
+       
+       if [ "$PKGMK_DOWNLOAD_ONLY" = "yes" ]; then
+               download_source
+               exit 0
+       fi
+       
+       if [ "$PKGMK_UP_TO_DATE" = "yes" ]; then
+               if [ "`build_needed`" = "yes" ]; then
+                       info "Package '$TARGET' is not up to date."
+               else
+                       info "Package '$TARGET' is up to date."
+               fi
+               exit 0
+       fi
+       
+       if [ "`build_needed`" = "no" ] && [ "$PKGMK_FORCE" = "no" ] && [ "$PKGMK_CHECK_MD5SUM" = "no" ]; then
+               info "Package '$TARGET' is up to date."
+       else
+               download_source
+               build_package
+       fi
+       
+       if [ "$PKGMK_INSTALL" != "no" ]; then
+               install_package
+       fi
+       
+       exit 0
+}
+
+trap "interrupted" SIGHUP SIGINT SIGQUIT SIGTERM
+
+export LC_ALL=POSIX
+
+readonly PKGMK_VERSION="#VERSION#"
+readonly PKGMK_COMMAND="$0"
+readonly PKGMK_ROOT="$PWD"
+
+PKGMK_CONFFILE="/etc/pkgmk.conf"
+PKGMK_PKGFILE="Pkgfile"
+PKGMK_FOOTPRINT=".footprint"
+PKGMK_MD5SUM=".md5sum"
+PKGMK_NOSTRIP=".nostrip"
+
+PKGMK_SOURCE_DIR="$PWD"
+PKGMK_PACKAGE_DIR="$PWD"
+PKGMK_WORK_DIR="$PWD/work"
+
+PKGMK_INSTALL="no"
+PKGMK_RECURSIVE="no"
+PKGMK_DOWNLOAD="no"
+PKGMK_DOWNLOAD_ONLY="no"
+PKGMK_UP_TO_DATE="no"
+PKGMK_UPDATE_FOOTPRINT="no"
+PKGMK_IGNORE_FOOTPRINT="no"
+PKGMK_FORCE="no"
+PKGMK_KEEP_WORK="no"
+PKGMK_UPDATE_MD5SUM="no"
+PKGMK_IGNORE_MD5SUM="no"
+PKGMK_CHECK_MD5SUM="no"
+PKGMK_NO_STRIP="no"
+PKGMK_CLEAN="no"
+
+main "$@"
+
+# End of file
diff --git a/pkgrm.8.in b/pkgrm.8.in
new file mode 100644 (file)
index 0000000..e7b46d2
--- /dev/null
@@ -0,0 +1,27 @@
+.TH pkgrm 8 "" "pkgutils #VERSION#" ""
+.SH NAME
+pkgrm \- remove software package
+.SH SYNOPSIS
+\fBpkgrm [options] <package>\fP
+.SH DESCRIPTION
+\fBpkgrm\fP is a \fIpackage management\fP utility, which
+removes/uninstalls a previously installed software packages.
+.SH OPTIONS
+.TP
+.B "\-r, \-\-root <path>"
+Specify alternative installation root (default is "/"). This
+should be used if you want to remove a package from a temporary 
+mounted partition, which is "owned" by another system. By using
+this option you not only specify where the software is installed,
+but you also specify which package database to use.
+.TP
+.B "\-v, \-\-version"
+Print version and exit.
+.TP
+.B "\-h, \-\-help"
+Print help and exit.
+.SH SEE ALSO
+pkgadd(8), pkginfo(8), pkgmk(8), rejmerge(8)
+.SH COPYRIGHT
+pkgrm (pkgutils) is Copyright (c) 2000-2005 Per Liden and is licensed through
+the GNU General Public License. Read the COPYING file for the complete license.
diff --git a/pkgrm.cc b/pkgrm.cc
new file mode 100644 (file)
index 0000000..a779f71
--- /dev/null
+++ b/pkgrm.cc
@@ -0,0 +1,78 @@
+//
+//  pkgutils
+// 
+//  Copyright (c) 2000-2005 Per Liden
+// 
+//  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.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+//  USA.
+//
+
+#include "pkgrm.h"
+#include <unistd.h>
+
+void pkgrm::run(int argc, char** argv)
+{
+       //
+       // Check command line options
+       //
+       string o_package;
+       string o_root;
+
+       for (int i = 1; i < argc; i++) {
+               string option(argv[i]);
+               if (option == "-r" || option == "--root") {
+                       assert_argument(argv, argc, i);
+                       o_root = argv[i + 1];
+                       i++;
+               } else if (option[0] == '-' || !o_package.empty()) {
+                       throw runtime_error("invalid option " + option);
+               } else {
+                       o_package = option;
+               }
+       }
+
+       if (o_package.empty())
+               throw runtime_error("option missing");
+
+       //
+       // Check UID
+       //
+       if (getuid())
+               throw runtime_error("only root can remove packages");
+
+       //
+       // Remove package
+       //
+       {
+               db_lock lock(o_root, true);
+               db_open(o_root);
+
+               if (!db_find_pkg(o_package))
+                       throw runtime_error("package " + o_package + " not installed");
+
+               db_rm_pkg(o_package);
+               ldconfig();
+               db_commit();
+       }
+}
+
+void pkgrm::print_help() const
+{
+       cout << "usage: " << utilname << " [options] <package>" << endl
+            << "options:" << endl
+            << "  -r, --root <path>   specify alternative installation root" << endl
+            << "  -v, --version       print version and exit" << endl
+            << "  -h, --help          print help and exit" << endl;
+}
diff --git a/pkgrm.h b/pkgrm.h
new file mode 100644 (file)
index 0000000..3958367
--- /dev/null
+++ b/pkgrm.h
@@ -0,0 +1,34 @@
+//
+//  pkgutils
+// 
+//  Copyright (c) 2000-2005 Per Liden
+// 
+//  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.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+//  USA.
+//
+
+#ifndef PKGRM_H
+#define PKGRM_H
+
+#include "pkgutil.h"
+
+class pkgrm : public pkgutil {
+public:
+       pkgrm() : pkgutil("pkgrm") {}
+       virtual void run(int argc, char** argv);
+       virtual void print_help() const;
+};
+
+#endif /* PKGRM_H */
diff --git a/pkgutil.cc b/pkgutil.cc
new file mode 100644 (file)
index 0000000..571dd6e
--- /dev/null
@@ -0,0 +1,754 @@
+//
+//  pkgutils
+// 
+//  Copyright (c) 2000-2005 Per Liden
+// 
+//  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.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+//  USA.
+//
+
+#include "pkgutil.h"
+#include <iostream>
+#include <fstream>
+#include <iterator>
+#include <algorithm>
+#include <cstdio>
+#include <cstring>
+#include <cerrno>
+#include <csignal>
+#include <ext/stdio_filebuf.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/file.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <zlib.h>
+#include <libgen.h>
+#include <libtar.h>
+
+using __gnu_cxx::stdio_filebuf;
+
+static tartype_t gztype = {
+       (openfunc_t)unistd_gzopen,
+       (closefunc_t)gzclose,
+       (readfunc_t)gzread,
+       (writefunc_t)gzwrite
+};
+
+pkgutil::pkgutil(const string& name)
+       : utilname(name)
+{
+       // Ignore signals
+       struct sigaction sa;
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = SIG_IGN;
+       sigaction(SIGHUP, &sa, 0);
+       sigaction(SIGINT, &sa, 0);
+       sigaction(SIGQUIT, &sa, 0);
+       sigaction(SIGTERM, &sa, 0);
+}
+
+void pkgutil::db_open(const string& path)
+{
+       // Read database
+       root = trim_filename(path + "/");
+       const string filename = root + PKG_DB;
+
+       int fd = open(filename.c_str(), O_RDONLY);
+       if (fd == -1)
+               throw runtime_error_with_errno("could not open " + filename);
+
+       stdio_filebuf<char> filebuf(fd, ios::in, getpagesize());
+       istream in(&filebuf);
+       if (!in)
+               throw runtime_error_with_errno("could not read " + filename);
+
+       while (!in.eof()) {
+               // Read record
+               string name;
+               pkginfo_t info;
+               getline(in, name);
+               getline(in, info.version);
+               for (;;) {
+                       string file;
+                       getline(in, file);
+         
+                       if (file.empty())
+                               break; // End of record
+         
+                       info.files.insert(info.files.end(), file);
+               }
+               if (!info.files.empty())
+                       packages[name] = info;
+       }
+
+#ifndef NDEBUG
+       cerr << packages.size() << " packages found in database" << endl;
+#endif
+}
+
+void pkgutil::db_commit()
+{
+       const string dbfilename = root + PKG_DB;
+       const string dbfilename_new = dbfilename + ".incomplete_transaction";
+       const string dbfilename_bak = dbfilename + ".backup";
+
+       // Remove failed transaction (if it exists)
+       if (unlink(dbfilename_new.c_str()) == -1 && errno != ENOENT)
+               throw runtime_error_with_errno("could not remove " + dbfilename_new);
+
+       // Write new database
+       int fd_new = creat(dbfilename_new.c_str(), 0444);
+       if (fd_new == -1)
+               throw runtime_error_with_errno("could not create " + dbfilename_new);
+
+       stdio_filebuf<char> filebuf_new(fd_new, ios::out, getpagesize());
+       ostream db_new(&filebuf_new);
+       for (packages_t::const_iterator i = packages.begin(); i != packages.end(); ++i) {
+               if (!i->second.files.empty()) {
+                       db_new << i->first << "\n";
+                       db_new << i->second.version << "\n";
+                       copy(i->second.files.begin(), i->second.files.end(), ostream_iterator<string>(db_new, "\n"));
+                       db_new << "\n";
+               }
+       }
+
+       db_new.flush();
+
+       // Make sure the new database was successfully written
+       if (!db_new)
+               throw runtime_error("could not write " + dbfilename_new);
+
+       // Synchronize file to disk
+       if (fsync(fd_new) == -1)
+               throw runtime_error_with_errno("could not synchronize " + dbfilename_new);
+
+       // Relink database backup
+       if (unlink(dbfilename_bak.c_str()) == -1 && errno != ENOENT)
+               throw runtime_error_with_errno("could not remove " + dbfilename_bak);   
+       if (link(dbfilename.c_str(), dbfilename_bak.c_str()) == -1)
+               throw runtime_error_with_errno("could not create " + dbfilename_bak);
+
+       // Move new database into place
+       if (rename(dbfilename_new.c_str(), dbfilename.c_str()) == -1)
+               throw runtime_error_with_errno("could not rename " + dbfilename_new + " to " + dbfilename);
+
+#ifndef NDEBUG
+       cerr << packages.size() << " packages written to database" << endl;
+#endif
+}
+
+void pkgutil::db_add_pkg(const string& name, const pkginfo_t& info)
+{
+       packages[name] = info;
+}
+
+bool pkgutil::db_find_pkg(const string& name)
+{
+       return (packages.find(name) != packages.end());
+}
+
+void pkgutil::db_rm_pkg(const string& name)
+{
+       set<string> files = packages[name].files;
+       packages.erase(name);
+
+#ifndef NDEBUG
+       cerr << "Removing package phase 1 (all files in package):" << endl;
+       copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
+       cerr << endl;
+#endif
+
+       // Don't delete files that still have references
+       for (packages_t::const_iterator i = packages.begin(); i != packages.end(); ++i)
+               for (set<string>::const_iterator j = i->second.files.begin(); j != i->second.files.end(); ++j)
+                       files.erase(*j);
+
+#ifndef NDEBUG
+       cerr << "Removing package phase 2 (files that still have references excluded):" << endl;
+       copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
+       cerr << endl;
+#endif
+
+       // Delete the files
+       for (set<string>::const_reverse_iterator i = files.rbegin(); i != files.rend(); ++i) {
+               const string filename = root + *i;
+               if (file_exists(filename) && remove(filename.c_str()) == -1) {
+                       const char* msg = strerror(errno);
+                       cerr << utilname << ": could not remove " << filename << ": " << msg << endl;
+               }
+       }
+}
+
+void pkgutil::db_rm_pkg(const string& name, const set<string>& keep_list)
+{
+       set<string> files = packages[name].files;
+       packages.erase(name);
+
+#ifndef NDEBUG
+       cerr << "Removing package phase 1 (all files in package):" << endl;
+       copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
+       cerr << endl;
+#endif
+
+       // Don't delete files found in the keep list
+       for (set<string>::const_iterator i = keep_list.begin(); i != keep_list.end(); ++i)
+               files.erase(*i);
+
+#ifndef NDEBUG
+       cerr << "Removing package phase 2 (files that is in the keep list excluded):" << endl;
+       copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
+       cerr << endl;
+#endif
+
+       // Don't delete files that still have references
+       for (packages_t::const_iterator i = packages.begin(); i != packages.end(); ++i)
+               for (set<string>::const_iterator j = i->second.files.begin(); j != i->second.files.end(); ++j)
+                       files.erase(*j);
+
+#ifndef NDEBUG
+       cerr << "Removing package phase 3 (files that still have references excluded):" << endl;
+       copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
+       cerr << endl;
+#endif
+
+       // Delete the files
+       for (set<string>::const_reverse_iterator i = files.rbegin(); i != files.rend(); ++i) {
+               const string filename = root + *i;
+               if (file_exists(filename) && remove(filename.c_str()) == -1) {
+                       if (errno == ENOTEMPTY)
+                               continue;
+                       const char* msg = strerror(errno);
+                       cerr << utilname << ": could not remove " << filename << ": " << msg << endl;
+               }
+       }
+}
+
+void pkgutil::db_rm_files(set<string> files, const set<string>& keep_list)
+{
+       // Remove all references
+       for (packages_t::iterator i = packages.begin(); i != packages.end(); ++i)
+               for (set<string>::const_iterator j = files.begin(); j != files.end(); ++j)
+                       i->second.files.erase(*j);
+   
+#ifndef NDEBUG
+       cerr << "Removing files:" << endl;
+       copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
+       cerr << endl;
+#endif
+
+       // Don't delete files found in the keep list
+       for (set<string>::const_iterator i = keep_list.begin(); i != keep_list.end(); ++i)
+               files.erase(*i);
+
+       // Delete the files
+       for (set<string>::const_reverse_iterator i = files.rbegin(); i != files.rend(); ++i) {
+               const string filename = root + *i;
+               if (file_exists(filename) && remove(filename.c_str()) == -1) {
+                       if (errno == ENOTEMPTY)
+                               continue;
+                       const char* msg = strerror(errno);
+                       cerr << utilname << ": could not remove " << filename << ": " << msg << endl;
+               }
+       }
+}
+
+set<string> pkgutil::db_find_conflicts(const string& name, const pkginfo_t& info)
+{
+       set<string> files;
+   
+       // Find conflicting files in database
+       for (packages_t::const_iterator i = packages.begin(); i != packages.end(); ++i) {
+               if (i->first != name) {
+                       set_intersection(info.files.begin(), info.files.end(),
+                                        i->second.files.begin(), i->second.files.end(),
+                                        inserter(files, files.end()));
+               }
+       }
+       
+#ifndef NDEBUG
+       cerr << "Conflicts phase 1 (conflicts in database):" << endl;
+       copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
+       cerr << endl;
+#endif
+
+       // Find conflicting files in filesystem
+       for (set<string>::iterator i = info.files.begin(); i != info.files.end(); ++i) {
+               const string filename = root + *i;
+               if (file_exists(filename) && files.find(*i) == files.end())
+                       files.insert(files.end(), *i);
+       }
+
+#ifndef NDEBUG
+       cerr << "Conflicts phase 2 (conflicts in filesystem added):" << endl;
+       copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
+       cerr << endl;
+#endif
+
+       // Exclude directories
+       set<string> tmp = files;
+       for (set<string>::const_iterator i = tmp.begin(); i != tmp.end(); ++i) {
+               if ((*i)[i->length() - 1] == '/')
+                       files.erase(*i);
+       }
+
+#ifndef NDEBUG
+       cerr << "Conflicts phase 3 (directories excluded):" << endl;
+       copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
+       cerr << endl;
+#endif
+
+       // If this is an upgrade, remove files already owned by this package
+       if (packages.find(name) != packages.end()) {
+               for (set<string>::const_iterator i = packages[name].files.begin(); i != packages[name].files.end(); ++i)
+                       files.erase(*i);
+
+#ifndef NDEBUG
+               cerr << "Conflicts phase 4 (files already owned by this package excluded):" << endl;
+               copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
+               cerr << endl;
+#endif
+       }
+
+       return files;
+}
+
+pair<string, pkgutil::pkginfo_t> pkgutil::pkg_open(const string& filename) const
+{
+       pair<string, pkginfo_t> result;
+       unsigned int i;
+       TAR* t;
+
+       // Extract name and version from filename
+       string basename(filename, filename.rfind('/') + 1);
+       string name(basename, 0, basename.find(VERSION_DELIM));
+       string version(basename, 0, basename.rfind(PKG_EXT));
+       version.erase(0, version.find(VERSION_DELIM) == string::npos ? string::npos : version.find(VERSION_DELIM) + 1);
+   
+       if (name.empty() || version.empty())
+               throw runtime_error("could not determine name and/or version of " + basename + ": Invalid package name");
+
+       result.first = name;
+       result.second.version = version;
+
+       if (tar_open(&t, const_cast<char*>(filename.c_str()), &gztype, O_RDONLY, 0, TAR_GNU) == -1)
+               throw runtime_error_with_errno("could not open " + filename);
+
+       for (i = 0; !th_read(t); ++i) {
+               result.second.files.insert(result.second.files.end(), th_get_pathname(t));
+               if (TH_ISREG(t) && tar_skip_regfile(t))
+                       throw runtime_error_with_errno("could not read " + filename);
+       }
+   
+       if (i == 0) {
+               if (errno == 0)
+                       throw runtime_error("empty package");
+               else
+                       throw runtime_error("could not read " + filename);
+       }
+
+       tar_close(t);
+
+       return result;
+}
+
+void pkgutil::pkg_install(const string& filename, const set<string>& keep_list) const
+{
+       TAR* t;
+       unsigned int i;
+
+       if (tar_open(&t, const_cast<char*>(filename.c_str()), &gztype, O_RDONLY, 0, TAR_GNU) == -1)
+               throw runtime_error_with_errno("could not open " + filename);
+
+       for (i = 0; !th_read(t); ++i) {
+               string archive_filename = th_get_pathname(t);
+               string reject_dir = trim_filename(root + string("/") + string(PKG_REJECTED));
+               string original_filename = trim_filename(root + string("/") + archive_filename);
+               string real_filename = original_filename;
+
+               // Check if file should be rejected
+               if (file_exists(real_filename) && keep_list.find(archive_filename) != keep_list.end())
+                       real_filename = trim_filename(reject_dir + string("/") + archive_filename);
+
+               // Extract file
+               if (tar_extract_file(t, const_cast<char*>(real_filename.c_str()))) {
+                       // If a file fails to install we just print an error message and
+                       // continue trying to install the rest of the package.
+                       const char* msg = strerror(errno);
+                       cerr << utilname << ": could not install " + archive_filename << ": " << msg << endl;
+                       continue;
+               }
+
+               // Check rejected file
+               if (real_filename != original_filename) {
+                       bool remove_file = false;
+
+                       // Directory
+                       if (TH_ISDIR(t))
+                               remove_file = permissions_equal(real_filename, original_filename);
+                       // Other files
+                       else
+                               remove_file = permissions_equal(real_filename, original_filename) &&
+                                       (file_empty(real_filename) || file_equal(real_filename, original_filename));
+
+                       // Remove rejected file or signal about its existence
+                       if (remove_file)
+                               file_remove(reject_dir, real_filename);
+                       else
+                               cout << utilname << ": rejecting " << archive_filename << ", keeping existing version" << endl;
+               }
+       }
+
+       if (i == 0) {
+               if (errno == 0)
+                       throw runtime_error("empty package");
+               else
+                       throw runtime_error("could not read " + filename);
+       }
+
+       tar_close(t);
+}
+
+void pkgutil::ldconfig() const
+{
+       // Only execute ldconfig if /etc/ld.so.conf exists
+       if (file_exists(root + LDCONFIG_CONF)) {
+               pid_t pid = fork();
+
+               if (pid == -1)
+                       throw runtime_error_with_errno("fork() failed");
+
+               if (pid == 0) {
+                       execl(LDCONFIG, LDCONFIG, "-r", root.c_str(), 0);
+                       const char* msg = strerror(errno);
+                       cerr << utilname << ": could not execute " << LDCONFIG << ": " << msg << endl;
+                       exit(EXIT_FAILURE);
+               } else {
+                       if (waitpid(pid, 0, 0) == -1)
+                               throw runtime_error_with_errno("waitpid() failed");
+               }
+       }
+}
+
+void pkgutil::pkg_footprint(string& filename) const
+{
+        unsigned int i;
+        TAR* t;
+
+        if (tar_open(&t, const_cast<char*>(filename.c_str()), &gztype, O_RDONLY, 0, TAR_GNU) == -1)
+                throw runtime_error_with_errno("could not open " + filename);
+
+        for (i = 0; !th_read(t); ++i) {
+               // Access permissions
+               if (TH_ISSYM(t)) {
+                       // Access permissions on symlinks differ among filesystems, e.g. XFS and ext2 have different.
+                       // To avoid getting different footprints we always use "lrwxrwxrwx".
+                       cout << "lrwxrwxrwx";
+               } else {
+                       cout << mtos(th_get_mode(t));
+               }
+
+               cout << '\t';
+
+               // User
+               uid_t uid = th_get_uid(t);
+               struct passwd* pw = getpwuid(uid);
+               if (pw)
+                       cout << pw->pw_name;
+               else
+                       cout << uid;
+
+               cout << '/';
+
+               // Group
+               gid_t gid = th_get_gid(t);
+               struct group* gr = getgrgid(gid);
+               if (gr)
+                       cout << gr->gr_name;
+               else
+                       cout << gid;
+
+               // Filename
+               cout << '\t' << th_get_pathname(t);
+
+               // Special cases
+               if (TH_ISSYM(t)) {
+                       // Symlink
+                       cout << " -> " << th_get_linkname(t);
+               } else if (TH_ISCHR(t) || TH_ISBLK(t)) {
+                       // Device
+                       cout << " (" << th_get_devmajor(t) << ", " << th_get_devminor(t) << ")";
+               } else if (TH_ISREG(t) && !th_get_size(t)) {
+                       // Empty regular file
+                       cout << " (EMPTY)";
+               }
+
+               cout << '\n';
+               
+                if (TH_ISREG(t) && tar_skip_regfile(t))
+                        throw runtime_error_with_errno("could not read " + filename);
+        }
+   
+        if (i == 0) {
+                if (errno == 0)
+                        throw runtime_error("empty package");
+                else
+                        throw runtime_error("could not read " + filename);
+        }
+
+        tar_close(t);
+}
+
+void pkgutil::print_version() const
+{
+       cout << utilname << " (pkgutils) " << VERSION << endl;
+}
+
+db_lock::db_lock(const string& root, bool exclusive)
+       : dir(0)
+{
+       const string dirname = trim_filename(root + string("/") + PKG_DIR);
+
+       if (!(dir = opendir(dirname.c_str())))
+               throw runtime_error_with_errno("could not read directory " + dirname);
+
+       if (flock(dirfd(dir), (exclusive ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) {
+               if (errno == EWOULDBLOCK)
+                       throw runtime_error("package database is currently locked by another process");
+               else
+                       throw runtime_error_with_errno("could not lock directory " + dirname);
+       }
+}
+
+db_lock::~db_lock()
+{
+       if (dir) {
+               flock(dirfd(dir), LOCK_UN);
+               closedir(dir);
+       }
+}
+
+void assert_argument(char** argv, int argc, int index)
+{
+       if (argc - 1 < index + 1)
+               throw runtime_error("option " + string(argv[index]) + " requires an argument");
+}
+
+string itos(unsigned int value)
+{
+       static char buf[20];
+       sprintf(buf, "%u", value);
+       return buf;
+}
+
+string mtos(mode_t mode)
+{
+       string s;
+
+       // File type
+       switch (mode & S_IFMT) {
+        case S_IFREG:  s += '-'; break; // Regular
+        case S_IFDIR:  s += 'd'; break; // Directory
+        case S_IFLNK:  s += 'l'; break; // Symbolic link
+        case S_IFCHR:  s += 'c'; break; // Character special
+        case S_IFBLK:  s += 'b'; break; // Block special
+        case S_IFSOCK: s += 's'; break; // Socket
+        case S_IFIFO:  s += 'p'; break; // Fifo
+        default:       s += '?'; break; // Unknown
+        }
+
+       // User permissions
+        s += (mode & S_IRUSR) ? 'r' : '-';
+        s += (mode & S_IWUSR) ? 'w' : '-';
+        switch (mode & (S_IXUSR | S_ISUID)) {
+        case S_IXUSR:           s += 'x'; break;
+        case S_ISUID:           s += 'S'; break;
+        case S_IXUSR | S_ISUID: s += 's'; break;
+        default:                s += '-'; break;
+        }
+
+        // Group permissions
+       s += (mode & S_IRGRP) ? 'r' : '-';
+        s += (mode & S_IWGRP) ? 'w' : '-';
+        switch (mode & (S_IXGRP | S_ISGID)) {
+        case S_IXGRP:           s += 'x'; break;
+        case S_ISGID:           s += 'S'; break;
+       case S_IXGRP | S_ISGID: s += 's'; break;
+        default:                s += '-'; break;
+        }
+
+        // Other permissions
+        s += (mode & S_IROTH) ? 'r' : '-';
+        s += (mode & S_IWOTH) ? 'w' : '-';
+        switch (mode & (S_IXOTH | S_ISVTX)) {
+        case S_IXOTH:           s += 'x'; break;
+        case S_ISVTX:           s += 'T'; break;
+        case S_IXOTH | S_ISVTX: s += 't'; break;
+        default:                s += '-'; break;
+        }
+
+       return s;
+}
+
+int unistd_gzopen(char* pathname, int flags, mode_t mode)
+{
+       char* gz_mode;
+   
+       switch (flags & O_ACCMODE) {
+       case O_WRONLY:
+               gz_mode = "w";
+               break;
+
+       case O_RDONLY:
+               gz_mode = "r";
+               break;
+
+       case O_RDWR:
+       default:
+               errno = EINVAL;
+               return -1;
+       }
+
+       int fd;
+       gzFile gz_file;
+
+       if ((fd = open(pathname, flags, mode)) == -1)
+               return -1;
+   
+       if ((flags & O_CREAT) && fchmod(fd, mode))
+               return -1;
+   
+       if (!(gz_file = gzdopen(fd, gz_mode))) {
+               errno = ENOMEM;
+               return -1;
+       }
+   
+       return (int)gz_file;
+}
+
+string trim_filename(const string& filename)
+{
+       string search("//");
+       string result = filename;
+
+       for (string::size_type pos = result.find(search); pos != string::npos; pos = result.find(search))
+               result.replace(pos, search.size(), "/");
+
+       return result;
+}
+
+bool file_exists(const string& filename)
+{
+       struct stat buf;
+       return !lstat(filename.c_str(), &buf);
+}
+
+bool file_empty(const string& filename)
+{
+       struct stat buf;
+
+       if (lstat(filename.c_str(), &buf) == -1)
+               return false;
+       
+       return (S_ISREG(buf.st_mode) && buf.st_size == 0);
+}
+
+bool file_equal(const string& file1, const string& file2)
+{
+       struct stat buf1, buf2;
+
+       if (lstat(file1.c_str(), &buf1) == -1)
+               return false;
+
+       if (lstat(file2.c_str(), &buf2) == -1)
+               return false;
+
+       // Regular files
+       if (S_ISREG(buf1.st_mode) && S_ISREG(buf2.st_mode)) {
+               ifstream f1(file1.c_str());
+               ifstream f2(file2.c_str());
+       
+               if (!f1 || !f2)
+                       return false;
+
+               while (!f1.eof()) {
+                       char buffer1[4096];
+                       char buffer2[4096];
+                       f1.read(buffer1, 4096);
+                       f2.read(buffer2, 4096);
+                       if (f1.gcount() != f2.gcount() ||
+                           memcmp(buffer1, buffer2, f1.gcount()) ||
+                           f1.eof() != f2.eof())
+                               return false;
+               }
+
+               return true;
+       }
+       // Symlinks
+       else if (S_ISLNK(buf1.st_mode) && S_ISLNK(buf2.st_mode)) {
+               char symlink1[MAXPATHLEN];
+               char symlink2[MAXPATHLEN];
+
+               memset(symlink1, 0, MAXPATHLEN);
+               memset(symlink2, 0, MAXPATHLEN);
+
+               if (readlink(file1.c_str(), symlink1, MAXPATHLEN - 1) == -1)
+                       return false;
+
+               if (readlink(file2.c_str(), symlink2, MAXPATHLEN - 1) == -1)
+                       return false;
+
+               return !strncmp(symlink1, symlink2, MAXPATHLEN);
+       }
+       // Character devices
+       else if (S_ISCHR(buf1.st_mode) && S_ISCHR(buf2.st_mode)) {
+               return buf1.st_dev == buf2.st_dev;
+       }
+       // Block devices
+       else if (S_ISBLK(buf1.st_mode) && S_ISBLK(buf2.st_mode)) {
+               return buf1.st_dev == buf2.st_dev;
+       }
+
+       return false;
+}
+
+bool permissions_equal(const string& file1, const string& file2)
+{
+       struct stat buf1;
+       struct stat buf2;
+
+       if (lstat(file1.c_str(), &buf1) == -1)
+               return false;
+
+       if (lstat(file2.c_str(), &buf2) == -1)
+               return false;
+       
+       return(buf1.st_mode == buf2.st_mode) &&
+               (buf1.st_uid == buf2.st_uid) &&
+               (buf1.st_gid == buf2.st_gid);
+}
+
+void file_remove(const string& basedir, const string& filename)
+{
+       if (filename != basedir && !remove(filename.c_str())) {
+               char* path = strdup(filename.c_str());
+               file_remove(basedir, dirname(path));
+               free(path);
+       }
+}
diff --git a/pkgutil.h b/pkgutil.h
new file mode 100644 (file)
index 0000000..9806900
--- /dev/null
+++ b/pkgutil.h
@@ -0,0 +1,108 @@
+//
+//  pkgutils
+// 
+//  Copyright (c) 2000-2005 Per Liden
+// 
+//  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.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+//  USA.
+//
+
+#ifndef PKGUTIL_H
+#define PKGUTIL_H
+
+#include <string>
+#include <set>
+#include <map>
+#include <iostream>
+#include <stdexcept>
+#include <cerrno>
+#include <cstring>
+#include <sys/types.h>
+#include <dirent.h>
+
+#define PKG_EXT         ".pkg.tar.gz"
+#define PKG_DIR         "var/lib/pkg"
+#define PKG_DB          "var/lib/pkg/db"
+#define PKG_REJECTED    "var/lib/pkg/rejected"
+#define VERSION_DELIM   '#'
+#define LDCONFIG        "/sbin/ldconfig"
+#define LDCONFIG_CONF   "/etc/ld.so.conf"
+
+using namespace std;
+
+class pkgutil {
+public:
+       struct pkginfo_t {
+               string version;
+               set<string> files;
+       };
+
+       typedef map<string, pkginfo_t> packages_t;
+
+       explicit pkgutil(const string& name);
+       virtual ~pkgutil() {}
+       virtual void run(int argc, char** argv) = 0;
+       virtual void print_help() const = 0;
+       void print_version() const;
+
+protected:
+       // Database
+       void db_open(const string& path);
+       void db_commit();
+       void db_add_pkg(const string& name, const pkginfo_t& info);
+       bool db_find_pkg(const string& name);
+       void db_rm_pkg(const string& name);
+       void db_rm_pkg(const string& name, const set<string>& keep_list);
+       void db_rm_files(set<string> files, const set<string>& keep_list);
+       set<string> db_find_conflicts(const string& name, const pkginfo_t& info);
+
+       // Tar.gz
+       pair<string, pkginfo_t> pkg_open(const string& filename) const;
+       void pkg_install(const string& filename, const set<string>& keep_list) const;
+       void pkg_footprint(string& filename) const;
+       void ldconfig() const;
+
+       string utilname;
+       packages_t packages;
+       string root;
+};
+
+class db_lock {
+public:
+       db_lock(const string& root, bool exclusive);
+       ~db_lock();
+private:
+       DIR* dir;
+};
+
+class runtime_error_with_errno : public runtime_error {
+public:
+       explicit runtime_error_with_errno(const string& msg) throw()
+               : runtime_error(msg + string(": ") + strerror(errno)) {}
+};
+
+// Utility functions
+void assert_argument(char** argv, int argc, int index);
+string itos(unsigned int value);
+string mtos(mode_t mode);
+int unistd_gzopen(char* pathname, int flags, mode_t mode);
+string trim_filename(const string& filename);
+bool file_exists(const string& filename);
+bool file_empty(const string& filename);
+bool file_equal(const string& file1, const string& file2);
+bool permissions_equal(const string& file1, const string& file2);
+void file_remove(const string& basedir, const string& filename);
+
+#endif /* PKGUTIL_H */
diff --git a/rejmerge.8.in b/rejmerge.8.in
new file mode 100644 (file)
index 0000000..210d2c0
--- /dev/null
@@ -0,0 +1,77 @@
+.TH rejmerge 8 "" "pkgutils #VERSION#" ""
+.SH NAME
+rejmerge \- merge files that were rejected during package upgrades
+.SH SYNOPSIS
+\fBrejmerge [options]\fP
+.SH DESCRIPTION
+\fBrejmerge\fP is a \fIpackage management\fP utility that helps you merge files that were rejected
+during package upgrades. For each rejected file found in \fI/var/lib/pkg/rejected/\fP, \fBrejmerge\fP
+will display the difference between the installed version and the rejected version. The user can then
+choose to keep the installed version, upgrade to the rejected version or perform a merge of the two.
+
+.SH OPTIONS
+.TP
+.B "\-r, \-\-root <path>"
+Specify alternative root (default is "/"). This should be used
+if you want to merge rejected files on a temporary mounted partition,
+which is "owned" by another system.
+.TP
+.B "\-v, \-\-version"
+Print version and exit.
+.TP
+.B "\-h, \-\-help"
+Print help and exit.
+.SH CONFIGURATION
+When \fBrejmerge\fP is started it will source \fI/etc/rejmerge.conf\fP.
+This file can be used to alter the way \fBrejmerge\fP displays file differences and performs file
+merges. Changing the default behaviour is done by re-defining the shell functions \fBrejmerge_diff()\fP
+and/or \fBrejmerge_merge()\fP.
+.TP
+.B rejmerge_diff()
+This function is executed once for each rejected file. Arguments \fB$1\fP and \fB$2\fP contain the paths
+to the installed and rejected files. Argument \fB$3\fP contains the path to a temporary file where this
+function should write its result. The contents of the temporary file will later be presented to the user
+as the difference between the two files.
+.TP
+.B rejmerge_merge()
+This function is executed when the user chooses to merge two files. Arguments \fB$1\fP and \fB$2\fP
+contain the paths to the installed and rejected files. Argument \fB$3\fP contains the path to a temporary
+file where this function should write its result. The contents of the temporary file will later be
+presented to the user as the merge result.
+This function also has the option to set the variable \fB$REJMERGE_MERGE_INFO\fP. The contents of this
+variable will be displayed as informational text after a merge has been performed. Its purpose is to provide
+information about the merge, e.g. "5 merge conflicts found".
+
+.PP
+Example:
+
+.nf
+#
+# /etc/rejmerge.conf: rejmerge(8) configuration
+#
+
+rejmerge_diff() {
+       # Use diff(1) to produce side-by-side output
+       diff -y $1 $2 > $3
+}
+
+rejmerge_merge() {
+       # Use sdiff(1) to merge
+       sdiff -o $3 $1 $2
+}
+
+# End of file
+.fi
+
+.SH FILES
+.TP
+.B "/etc/rejmerge.conf"
+Configuration file.
+.TP
+.B "/var/lib/pkg/rejected/"
+Directory where rejected files are stored.
+.SH SEE ALSO
+pkgadd(8), pkgrm(8), pkginfo(8), pkgmk(8)
+.SH COPYRIGHT
+rejmerge (pkgutils) is Copyright (c) 2000-2005 Per Liden and is licensed through
+the GNU General Public License. Read the COPYING file for the complete license.
diff --git a/rejmerge.conf b/rejmerge.conf
new file mode 100644 (file)
index 0000000..c80af34
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# /etc/rejmerge.conf: rejmerge(8) configuration
+#
+
+# End of file
diff --git a/rejmerge.in b/rejmerge.in
new file mode 100755 (executable)
index 0000000..e95f4df
--- /dev/null
@@ -0,0 +1,315 @@
+#!/bin/bash
+#
+#  rejmerge (pkgutils)
+# 
+#  Copyright (c) 2000-2005 Per Liden
+# 
+#  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.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+#  USA.
+#
+
+info_n() {
+       echo -n "=======> $1"
+}
+
+info() {
+       info_n "$1"
+       echo
+}
+
+interrupted() {
+       echo ""
+       info "Aborted."
+       exit 1
+}
+
+atexit() {
+       if [ -e "$TMPFILE" ]; then
+               rm -f "$TMPFILE"
+       fi
+}
+
+rejmerge_diff() {
+       diff -u "$1" "$2" > "$3"
+}
+
+rejmerge_merge() {
+       diff --old-group-format="%<" \
+            --new-group-format="%>" \
+            --changed-group-format="<<<<< MERGE CONFLICT $1 >>>>>
+%<<<<<< MERGE CONFLICT $2 >>>>>
+%><<<<< END MERGE CONFLICT >>>>>
+" \
+       "$1" "$2" > "$3"
+
+       REJMERGE_MERGE_INFO="$(grep -c '^<<<<< END MERGE CONFLICT >>>>>$' "$3") merge conflict(s)."
+}
+
+permissions_menu() {
+       while true; do
+               info "Access permissions $1"
+               stat -c '%A  %U %G  %n' "$1"
+               stat -c '%A  %U %G  %n' "$2"
+               while true; do
+                       info_n "[K]eep [U]pgrade [D]iff [S]kip? "
+                       read -n1 CMD
+                       echo
+
+                       case "$CMD" in
+                       k|K)    chown --reference="$1" "$2"
+                               chmod --reference="$1" "$2"
+                               break 2
+                               ;;
+                       u|U)    chown --reference="$2" "$1"
+                               chmod --reference="$2" "$1"
+                               break 2
+                               ;;
+                       d|D)    break 1
+                               ;;
+                       s|S)    break 2
+                               ;;
+                       esac
+               done
+       done
+}
+
+merge_menu() {
+       rejmerge_merge "$1" "$2" "$TMPFILE"
+
+       while true; do
+               info "Merged $1"
+               cat "$TMPFILE" | more
+
+               if [ "$REJMERGE_MERGE_INFO" ]; then
+                       info "$REJMERGE_MERGE_INFO"
+                       unset REJMERGE_MERGE_INFO
+               fi
+
+               while true; do
+                       info_n "[I]nstall [E]dit [V]iew [S]kip? "
+                       read -n1 CMD
+                       echo
+
+                       case "$CMD" in
+                       i|I)    chmod --reference="$1" "$TMPFILE"
+                               mv -f "$TMPFILE" "$1"
+                               rm -f "$2"
+                               break 2
+                               ;;
+                       e|E)    $EDITOR "$TMPFILE"
+                               break 1
+                               ;;
+                       v|V)    break 1
+                               ;;
+                       s|S)    break 2
+                               ;;
+                       esac
+               done
+       done
+
+       : > "$TMPFILE"
+}
+
+diff_menu() {
+       rejmerge_diff "$1" "$2" "$TMPFILE"
+
+       while true; do
+               info "$1"
+               cat "$TMPFILE" | more
+               while true; do
+                       info_n "[K]eep [U]pgrade [M]erge [D]iff [S]kip? "
+                       read -n1 CMD
+                       echo
+
+                       case "$CMD" in
+                       k|K)    rm -f "$2"
+                               break 2
+                               ;;
+                       u|U)    mv -f "$2" "$1"
+                               break 2
+                               ;;
+                       m|M)    merge_menu "$1" "$2"
+                               break 2
+                               ;;
+                       d|D)    break 1
+                               ;;
+                       s|S)    break 2
+                               ;;
+                       esac
+               done
+       done
+
+       : > "$TMPFILE"
+}
+
+file_menu() {
+       while true; do
+               info "$1"
+               file "$1" "$2"
+               while true; do
+                       info_n "[K]eep [U]pgrade [D]iff [S]kip? "
+                       read -n1 CMD
+                       echo
+
+                       case "$CMD" in
+                       k|K)    rm -f "$2"
+                               break 2
+                               ;;
+                       u|U)    mv -f "$2" "$1"
+                               break 2
+                               ;;
+                       d|D)    break 1
+                               ;;
+                       s|S)    break 2
+                               ;;
+                       esac
+               done
+       done
+}
+
+print_help() {
+       echo "usage: $REJMERGE_COMMAND [options]"
+       echo "options:"
+       echo "  -r,   --root <path>         specify alternative root"
+       echo "  -v,   --version             print version and exit "
+       echo "  -h,   --help                print help and exit"
+}
+
+parse_options() {
+       while [ "$1" ]; do
+               case $1 in
+                       -r|--root)
+                               if [ ! "$2" ]; then
+                                       echo "$REJMERGE_COMMAND: option $1 requires an argument"
+                                       exit 1
+                               fi
+                               REJMERGE_ROOT="$2"
+                               REJMERGE_CONF="$2$REJMERGE_CONF"
+                               REJECTED_DIR="$2$REJECTED_DIR"
+                               shift ;;
+                       -v|--version)
+                               echo "$REJMERGE_COMMAND (pkgutils) $REJMERGE_VERSION"
+                               exit 0 ;;
+                       -h|--help)
+                               print_help
+                               exit 0 ;;
+                       *)
+                               echo "$REJMERGE_COMMAND: invalid option $1"
+                               exit 1 ;;
+               esac
+               shift
+       done
+
+       if [ ! -d "$REJECTED_DIR" ]; then
+               echo "$REJMERGE_COMMAND: $REJECTED_DIR not found"
+               exit 1
+       fi
+}
+
+files_regular() {
+       local STAT_FILE1=$(stat -c '%F' "$1")
+       local STAT_FILE2=$(stat -c '%F' "$2")
+
+       if [ "$STAT_FILE1" != "regular file" ]; then
+               return 1
+       fi
+
+       if [ "$STAT_FILE2" != "regular file" ]; then
+               return 1
+       fi
+
+       return 0
+}
+
+main() {
+       parse_options "$@"
+
+       if [ "$UID" != "0" ]; then
+               echo "$REJMERGE_COMMAND: only root can merge rejected files"
+               exit 1
+       fi
+
+       # Read configuration
+       if [ -f "$REJMERGE_CONF" ]; then
+               . "$REJMERGE_CONF"
+       fi
+       
+       REJECTED_FILES_FOUND="no"
+
+       # Check files
+       for REJECTED_FILE in $(find $REJECTED_DIR ! -type d); do
+               INSTALLED_FILE="$REJMERGE_ROOT${REJECTED_FILE##$REJECTED_DIR}"
+
+               # Remove rejected file if there is no installed version
+               if [ ! -e "$INSTALLED_FILE" ]; then
+                       rm -f "$REJECTED_FILE"
+                       continue
+               fi
+
+               # Check permissions
+               local STAT_FILE1=$(stat -c '%A %U %G' "$INSTALLED_FILE")
+               local STAT_FILE2=$(stat -c '%A %U %G' "$REJECTED_FILE")
+
+               if [ "$STAT_FILE1" != "$STAT_FILE2" ]; then
+                       REJECTED_FILES_FOUND="yes"
+                       permissions_menu "$INSTALLED_FILE" "$REJECTED_FILE"
+               fi
+
+               # Check file types
+               if files_regular "$INSTALLED_FILE" "$REJECTED_FILE"; then
+                       # Both files are regular
+                       if cmp -s "$INSTALLED_FILE" "$REJECTED_FILE"; then
+                               rm -f "$REJECTED_FILE"
+                       else
+                               REJECTED_FILES_FOUND="yes"
+                               diff_menu "$INSTALLED_FILE" "$REJECTED_FILE"
+                       fi
+               else
+                       # At least one file is non-regular
+                       REJECTED_FILES_FOUND="yes"
+                       file_menu "$INSTALLED_FILE" "$REJECTED_FILE"
+               fi
+       done
+
+       # Remove empty directories
+       for DIR in $(find $REJECTED_DIR -depth -type d); do
+               if [ "$DIR" != "$REJECTED_DIR" ]; then
+                       rmdir "$DIR" &> /dev/null
+               fi
+       done
+
+       if [ "$REJECTED_FILES_FOUND" = "no" ]; then
+               echo "Nothing to merge"
+       fi
+
+       exit 0
+}
+
+trap "interrupted" SIGHUP SIGINT SIGQUIT SIGTERM
+trap "atexit" EXIT
+
+export LC_ALL=POSIX
+
+readonly REJMERGE_VERSION="#VERSION#"
+readonly REJMERGE_COMMAND="${0##*/}"
+REJMERGE_ROOT=""
+REJMERGE_CONF="/etc/rejmerge.conf"
+REJECTED_DIR="/var/lib/pkg/rejected"
+EDITOR=${EDITOR:-vi}
+TMPFILE=$(mktemp) || exit 1
+
+main "$@"
+
+# End of file