Initial import

This commit is contained in:
Simone Rota 2005-11-11 23:40:48 +01:00
commit 9ac667e68d
27 changed files with 3640 additions and 0 deletions

340
COPYING Normal file
View File

@ -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.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for 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.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
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.

227
ChangeLog Normal file
View File

@ -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)

111
Makefile Normal file
View File

@ -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

28
README Normal file
View File

@ -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.

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

BIN
libtar-1.2.11.tar.gz Normal file

Binary file not shown.

76
main.cc Normal file
View File

@ -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;
}

68
pkgadd.8.in Normal file
View File

@ -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.

206
pkgadd.cc Normal file
View File

@ -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;
}

21
pkgadd.conf Normal file
View File

@ -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

49
pkgadd.h Normal file
View File

@ -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 */

41
pkginfo.8.in Normal file
View File

@ -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.

154
pkginfo.cc Normal file
View File

@ -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;
}

34
pkginfo.h Normal file
View File

@ -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 */

92
pkgmk.8.in Normal file
View File

@ -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.

15
pkgmk.conf Normal file
View File

@ -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

653
pkgmk.in Executable file
View File

@ -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

27
pkgrm.8.in Normal file
View File

@ -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.

78
pkgrm.cc Normal file
View File

@ -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;
}

34
pkgrm.h Normal file
View File

@ -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 */

754
pkgutil.cc Normal file
View File

@ -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);
}
}

108
pkgutil.h Normal file
View File

@ -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 */

77
rejmerge.8.in Normal file
View File

@ -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.

5
rejmerge.conf Normal file
View File

@ -0,0 +1,5 @@
#
# /etc/rejmerge.conf: rejmerge(8) configuration
#
# End of file

315
rejmerge.in Executable file
View File

@ -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