From: Simone Rota Date: Fri, 11 Nov 2005 22:40:48 +0000 (+0100) Subject: Initial import X-Git-Url: http://gitweb/?a=commitdiff_plain;h=9ac667e68d3e36eb99272eac57219002ee2318e6;p=pkgutils-cross.git Initial import --- 9ac667e68d3e36eb99272eac57219002ee2318e6 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..96e4591 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + 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. + + + Copyright (C) 19yy + + 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. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..7fffff1 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,227 @@ +5.20 - Released 2005-05-04 + - pkgadd/rejmerge will now consider user, group and access + permissions on rejected files. + +5.19 - Released 2005-03-29 + - pkgadd: improved support for automatically removing + rejected files that are identical to already installed files. + - pkgmk: added support for resuming interrupted downloads. + Thanks to Johannes Winkelmann + - 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 + +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 + - 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 + +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 . + - 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 . + - Upgraded bundled libtar from 1.2.9 to 1.2.10. + +5.2 - Released 2002-12-08 + - pkgmk: exports LC_ALL=POSIX to force utilities to use a + neutral locate. + - Upgraded bundled libtar from 1.2.8 to 1.2.9. + +5.1 - Released 2002-10-27 + - Upgraded bundled libtar from 1.2.5 to 1.2.8. + - pkgadd/pkgrm/pkginfo: Added file-locking on database to + prevent more than one instance of pkgadd/pkgrm/pkginfo from + running at the same time. + - pkgadd: Fixed a bug in libtar that caused segmentation fault + when extracting files whose filenames contains characters + with ascii value > 127. + - pkgmk: Fixed bug which caused suid/sgid binaries to become + unstripped. + - pkgmk: Added option -ns/--no-strip. Use it to avoid stripping + binaries in a package. + - pkginfo: -o/--owner does not require the whole path to the + file anymore. + +5.0 - Released 2002-09-09 + - Now requires GCC 3.2 to compile (due to STL incompatibility). + - pkginfo: -o/--owner now prepends the current directory to + the file argument unless it starts with /. This feature is + disable when using the -r/--root option. + - pkgmk: The build() function will now be aborted as soon + as some command exits with an exit code other than 0 (zero). + - pkgmk: Binaries are now stripped automatically. + - pkgmk: Man pages are now compressed automatically. + - pkgmk: Symlinks are always given access permissions + lrwxrwxrwx in .footprint, regardless of the actual + access permissions. This avoids footprint problems + when using e.g. XFS. + +4.4 - Released 2002-06-30 + - Added option -cf, --config-file to pkgmk. + - Minor bugfixes. + +4.3 - Released 2002-06-11 + - Removed Pkgfile.local-feature which was added in 4.2. It + didn't work very well in some (common) situations. + - Corrected spelling errors in pkgmk. + +4.2 - Released 2002-05-17 + - Added support for Pkgfile.local, which enables users to + tweak packages by overriding parts of the original + Pkgfile. This is useful when pkgmk is used in CRUX's + ports system, where users will loose changes made to the + original Pkgfile the next time they update their ports + structure. + - Minor cleanups. + +4.1 - Released 2002-04-08 + - Added support for preventing selected files (typically + configuration files) from being overwritten when upgrading + a package. The file /etc/pkgadd.conf, contains a list of + rules with regular expressions specifying these files. These + rules will be consulted when executing pkgadd with the + option -u. Files that, according to the rules, shouldn't be + upgraded will instead be installed under + /var/lib/pkg/rejected/. The user can then examine, use and + remove these files manually if so desired. + - Added md5sum checking (.md5sum contains the MD5 checksum of + all source files). pkgmk uses this file to verify that + the (potentially downloaded) source files are correct. + - Upgraded bundled libtar from 1.2.4 to 1.2.5. + +4.0.1 - Released 2002-01-20 + - Removed warning "unable to remove XXX: Directory not empty" + when upgrading a package. + +4.0 - Released 2002-01-14 + - Packages are now identified by their names only (and + not by name and version as before). This makes it easier + for users to upgrade and remove packages. This, of course, + comes with a price. You can not install two packages with + the same name. + - The naming convention for packages is now: + name#version-release.pkg.tar.gz + The character '#' is not allowed in package names, since + it's used as the name/version delimiter. + - New database layout, which gives a more robust database + with a transaction-like behaviour. This implementation + will gurantee that the database will never be corrupted + even if the power fails when pkgadd/pkgrm is running. It + does however not guarantee that the database contents is + in sync with the filesystem if such a crash should occur. + This means that the database will _never_ loose track of + files that are installed, but it can (in case of a crash) + contain files that are actually not installed. Repeating + the pkgadd/pkgrm command that was running when the crash + occured will get the database in sync with the filesystem + again. + - pkgmk is now capable of downloading missing source files + (using wget) before building a package (option -d), given + that the URL is specified in the "source" variable. + - pkg.build was renamed to Pkgfile (to mimic make/Makefile). + - pkg.contents was renamed to .footprint. + - pkgmk is now capable of installing/upgrading a package if + the build was successful (option -i and -u). + - Lot's of minor fixes and cleanups. + +0.1 - 3.2.0 - Released 2000-05-10 - 2001-10-03 + (No change log was maintained during this time) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7517a2c --- /dev/null +++ b/Makefile @@ -0,0 +1,111 @@ +# +# pkgutils +# +# Copyright (c) 2000-2005 by 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. +# + +DESTDIR = +BINDIR = /usr/bin +MANDIR = /usr/man +ETCDIR = /etc + +VERSION = 5.21 +LIBTAR_VERSION = 1.2.11 + +CXXFLAGS += -DNDEBUG +CXXFLAGS += -O2 -Wall -pedantic -D_GNU_SOURCE -DVERSION=\"$(VERSION)\" \ + -Ilibtar-$(LIBTAR_VERSION)/lib -Ilibtar-$(LIBTAR_VERSION)/listhash + +LDFLAGS += -static -Llibtar-$(LIBTAR_VERSION)/lib -ltar -lz + +OBJECTS = main.o pkgutil.o pkgadd.o pkgrm.o pkginfo.o + +MANPAGES = pkgadd.8 pkgrm.8 pkginfo.8 pkgmk.8 rejmerge.8 + +LIBTAR = libtar-$(LIBTAR_VERSION)/lib/libtar.a + +all: pkgadd pkgmk rejmerge man + +$(LIBTAR): + (tar xzf libtar-$(LIBTAR_VERSION).tar.gz; \ + cd libtar-$(LIBTAR_VERSION); \ + patch -p1 < ../libtar-$(LIBTAR_VERSION)-fix_mem_leak.patch; \ + patch -p1 < ../libtar-$(LIBTAR_VERSION)-reduce_mem_usage.patch; \ + patch -p1 < ../libtar-$(LIBTAR_VERSION)-fix_linkname_overflow.patch; \ + LDFLAGS="" ./configure --disable-encap --disable-encap-install; \ + make) + +pkgadd: $(LIBTAR) .depend $(OBJECTS) + $(CXX) $(OBJECTS) -o $@ $(LDFLAGS) + +pkgmk: pkgmk.in + +rejmerge: rejmerge.in + +man: $(MANPAGES) + +mantxt: man $(MANPAGES:=.txt) + +%.8.txt: %.8 + nroff -mandoc -c $< | col -bx > $@ + +%: %.in + sed -e "s/#VERSION#/$(VERSION)/" $< > $@ + +.depend: + $(CXX) $(CXXFLAGS) -MM $(OBJECTS:.o=.cc) > .depend + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif + +.PHONY: install clean distclean dist + +dist: distclean + rm -rf /tmp/pkgutils-$(VERSION) + mkdir -p /tmp/pkgutils-$(VERSION) + cp -rf . /tmp/pkgutils-$(VERSION) + tar -C /tmp --exclude .svn -czvf ../pkgutils-$(VERSION).tar.gz pkgutils-$(VERSION) + rm -rf /tmp/pkgutils-$(VERSION) + +install: all + install -D -m0755 pkgadd $(DESTDIR)$(BINDIR)/pkgadd + install -D -m0644 pkgadd.conf $(DESTDIR)$(ETCDIR)/pkgadd.conf + install -D -m0755 pkgmk $(DESTDIR)$(BINDIR)/pkgmk + install -D -m0755 rejmerge $(DESTDIR)$(BINDIR)/rejmerge + install -D -m0644 pkgmk.conf $(DESTDIR)$(ETCDIR)/pkgmk.conf + install -D -m0644 rejmerge.conf $(DESTDIR)$(ETCDIR)/rejmerge.conf + install -D -m0644 pkgadd.8 $(DESTDIR)$(MANDIR)/man8/pkgadd.8 + install -D -m0644 pkgrm.8 $(DESTDIR)$(MANDIR)/man8/pkgrm.8 + install -D -m0644 pkginfo.8 $(DESTDIR)$(MANDIR)/man8/pkginfo.8 + install -D -m0644 pkgmk.8 $(DESTDIR)$(MANDIR)/man8/pkgmk.8 + install -D -m0644 rejmerge.8 $(DESTDIR)$(MANDIR)/man8/rejmerge.8 + ln -sf pkgadd $(DESTDIR)$(BINDIR)/pkgrm + ln -sf pkgadd $(DESTDIR)$(BINDIR)/pkginfo + +clean: + rm -f .depend + rm -f $(OBJECTS) + rm -f $(MANPAGES) + rm -f $(MANPAGES:=.txt) + +distclean: clean + rm -f pkgadd pkginfo pkgrm pkgmk rejmerge + rm -rf libtar-$(LIBTAR_VERSION) + +# End of file diff --git a/README b/README new file mode 100644 index 0000000..3fe0c15 --- /dev/null +++ b/README @@ -0,0 +1,28 @@ + + pkgutils - Package Management Utilities + + http://www.fukt.bth.se/~per/pkgutils/ + + +Description +----------- +pkgutils is a set of utilities (pkgadd, pkgrm, pkginfo, pkgmk and rejmerge), +which are used for managing software packages in Linux. It is developed for +and used by the CRUX distribution (http://crux.nu). + + +Building and installing +----------------------- +$ make +$ make install +or +$ make DESTDIR=/some/other/path install + + +Copyright +--------- +pkgutils is Copyright (c) 2000-2005 Per Liden and is licensed through the +GNU General Public License. Read the COPYING file for the complete license. + +pkgutils uses libtar, a library for reading/writing tar-files. This +library is Copyright (c) 1998-2003 Mark D. Roth. diff --git a/libtar-1.2.11-fix_linkname_overflow.patch b/libtar-1.2.11-fix_linkname_overflow.patch new file mode 100644 index 0000000..7386dd2 --- /dev/null +++ b/libtar-1.2.11-fix_linkname_overflow.patch @@ -0,0 +1,35 @@ +diff -ru libtar-1.2.11/lib/decode.c libtar-1.2.11-new/lib/decode.c +--- libtar-1.2.11/lib/decode.c 2004-08-18 22:12:06.888107160 +0200 ++++ libtar-1.2.11-new/lib/decode.c 2004-08-18 22:05:27.569812768 +0200 +@@ -42,6 +42,17 @@ + return filename; + } + ++char* ++th_get_linkname(TAR* t) ++{ ++ static char filename[MAXPATHLEN]; ++ ++ if (t->th_buf.gnu_longlink) ++ return t->th_buf.gnu_longlink; ++ ++ snprintf(filename, sizeof(filename), "%.100s", t->th_buf.linkname); ++ return filename; ++} + + uid_t + th_get_uid(TAR *t) +diff -ru libtar-1.2.11/lib/libtar.h libtar-1.2.11-new/lib/libtar.h +--- libtar-1.2.11/lib/libtar.h 2003-01-07 02:40:59.000000000 +0100 ++++ libtar-1.2.11-new/lib/libtar.h 2004-08-18 21:59:12.344855632 +0200 +@@ -184,9 +184,7 @@ + #define th_get_mtime(t) oct_to_int((t)->th_buf.mtime) + #define th_get_devmajor(t) oct_to_int((t)->th_buf.devmajor) + #define th_get_devminor(t) oct_to_int((t)->th_buf.devminor) +-#define th_get_linkname(t) ((t)->th_buf.gnu_longlink \ +- ? (t)->th_buf.gnu_longlink \ +- : (t)->th_buf.linkname) ++char *th_get_linkname(TAR *t); + char *th_get_pathname(TAR *t); + mode_t th_get_mode(TAR *t); + uid_t th_get_uid(TAR *t); diff --git a/libtar-1.2.11-fix_mem_leak.patch b/libtar-1.2.11-fix_mem_leak.patch new file mode 100644 index 0000000..0b09a18 --- /dev/null +++ b/libtar-1.2.11-fix_mem_leak.patch @@ -0,0 +1,26 @@ +diff -ru libtar-1.2.11/lib/decode.c libtar-1.2.11-new/lib/decode.c +--- libtar-1.2.11/lib/decode.c 2003-01-07 02:40:59.000000000 +0100 ++++ libtar-1.2.11-new/lib/decode.c 2003-10-03 15:02:44.000000000 +0200 +@@ -26,7 +26,7 @@ + char * + th_get_pathname(TAR *t) + { +- char filename[MAXPATHLEN]; ++ static char filename[MAXPATHLEN]; + + if (t->th_buf.gnu_longname) + return t->th_buf.gnu_longname; +@@ -35,11 +35,11 @@ + { + snprintf(filename, sizeof(filename), "%.155s/%.100s", + t->th_buf.prefix, t->th_buf.name); +- return strdup(filename); ++ return filename; + } + + snprintf(filename, sizeof(filename), "%.100s", t->th_buf.name); +- return strdup(filename); ++ return filename; + } + + diff --git a/libtar-1.2.11-reduce_mem_usage.patch b/libtar-1.2.11-reduce_mem_usage.patch new file mode 100644 index 0000000..db28ede --- /dev/null +++ b/libtar-1.2.11-reduce_mem_usage.patch @@ -0,0 +1,66 @@ +diff -ru libtar-1.2.11/lib/extract.c libtar-1.2.11-new/lib/extract.c +--- libtar-1.2.11/lib/extract.c 2003-03-03 00:58:07.000000000 +0100 ++++ libtar-1.2.11-new/lib/extract.c 2003-10-03 15:07:46.000000000 +0200 +@@ -28,14 +28,6 @@ + #endif + + +-struct linkname +-{ +- char ln_save[MAXPATHLEN]; +- char ln_real[MAXPATHLEN]; +-}; +-typedef struct linkname linkname_t; +- +- + static int + tar_set_file_perms(TAR *t, char *realname) + { +@@ -98,7 +90,9 @@ + tar_extract_file(TAR *t, char *realname) + { + int i; +- linkname_t *lnp; ++ char *lnp; ++ int pathname_len; ++ int realname_len; + + if (t->options & TAR_NOOVERWRITE) + { +@@ -137,11 +131,13 @@ + if (i != 0) + return i; + +- lnp = (linkname_t *)calloc(1, sizeof(linkname_t)); ++ pathname_len = strlen(th_get_pathname(t)) + 1; ++ realname_len = strlen(realname) + 1; ++ lnp = (char *)calloc(1, pathname_len + realname_len); + if (lnp == NULL) + return -1; +- strlcpy(lnp->ln_save, th_get_pathname(t), sizeof(lnp->ln_save)); +- strlcpy(lnp->ln_real, realname, sizeof(lnp->ln_real)); ++ strcpy(&lnp[0], th_get_pathname(t)); ++ strcpy(&lnp[pathname_len], realname); + #ifdef DEBUG + printf("tar_extract_file(): calling libtar_hash_add(): key=\"%s\", " + "value=\"%s\"\n", th_get_pathname(t), realname); +@@ -288,7 +284,7 @@ + { + char *filename; + char *linktgt = NULL; +- linkname_t *lnp; ++ char *lnp; + libtar_hashptr_t hp; + + if (!TH_ISLNK(t)) +@@ -304,8 +300,8 @@ + if (libtar_hash_getkey(t->h, &hp, th_get_linkname(t), + (libtar_matchfunc_t)libtar_str_match) != 0) + { +- lnp = (linkname_t *)libtar_hashptr_data(&hp); +- linktgt = lnp->ln_real; ++ lnp = (char *)libtar_hashptr_data(&hp); ++ linktgt = &lnp[strlen(lnp) + 1]; + } + else + linktgt = th_get_linkname(t); diff --git a/libtar-1.2.11.tar.gz b/libtar-1.2.11.tar.gz new file mode 100644 index 0000000..ed8f796 Binary files /dev/null and b/libtar-1.2.11.tar.gz differ diff --git a/main.cc b/main.cc new file mode 100644 index 0000000..1681564 --- /dev/null +++ b/main.cc @@ -0,0 +1,76 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +#if (__GNUC__ < 3) +#error This program requires GCC 3.x to compile. +#endif + +#include +#include +#include +#include +#include +#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 util(select_utility(name)); + + // Handle common options + for (int i = 1; i < argc; i++) { + string option(argv[i]); + if (option == "-v" || option == "--version") { + util->print_version(); + return EXIT_SUCCESS; + } else if (option == "-h" || option == "--help") { + util->print_help(); + return EXIT_SUCCESS; + } + } + + util->run(argc, argv); + } catch (runtime_error& e) { + cerr << name << ": " << e.what() << endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/pkgadd.8.in b/pkgadd.8.in new file mode 100644 index 0000000..1cfb195 --- /dev/null +++ b/pkgadd.8.in @@ -0,0 +1,68 @@ +.TH pkgadd 8 "" "pkgutils #VERSION#" "" +.SH NAME +pkgadd \- install software package +.SH SYNOPSIS +\fBpkgadd [options] \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 . +.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 " +Specify alternative installation root (default is "/"). This +should \fInot\fP be used as a way to install software into +e.g. /usr/local instead of /usr. Instead this should be used +if you want to install a package on a temporary mounted partition, +which is "owned" by another system. By using this option you not only +specify where the software should be installed, but you also +specify which package database to use. +.TP +.B "\-v, \-\-version" +Print version and exit. +.TP +.B "\-h, \-\-help" +Print help and exit. +.SH CONFIGURATION +When using \fBpkgadd\fP in upgrade mode (i.e. option -u is used) the +file \fI/etc/pkgadd.conf\fP will be read. This file can contain rules describing +how pkgadd should behave when doing upgrades. A rule is built out of three +fragments, \fIevent\fP, \fIpattern\fP and \fIaction\fP. The event describes +in what kind of situation this rule applies. Currently only one type of event is +supported, that is \fBUPGRADE\fP. The pattern is a regular expression and the action +applicable to the \fBUPGRADE\fP event is \fBYES\fP and \fBNO\fP. More than one rule of the same +event type is allowed, in which case the first rule will have the lowest priority and the last rule +will have the highest priority. Example: + +.nf +UPGRADE ^etc/.*$ NO +UPGRADE ^var/log/.*$ NO +UPGRADE ^etc/X11/.*$ YES +UPGRADE ^etc/X11/XF86Config$ NO +.fi + +The above example will cause pkgadd to never upgrade anything in /etc/ or /var/log/ (subdirectories included), +except files in /etc/X11/ (subdirectories included), unless it is the file /etc/X11/XF86Config. +The default rule is to upgrade everything, rules in this file are exceptions to that rule. +(NOTE! A \fIpattern\fP should never contain an initial "/" since you are referring to the files in the +package, not the files on the disk.) + +If pkgadd finds that a specific file should not be upgraded it will install it under \fI/var/lib/pkg/rejected/\fP. +The user is then free to examine/use/remove that file manually. +.SH FILES +.TP +.B "/etc/pkgadd.conf" +Configuration file. +.SH SEE ALSO +pkgrm(8), pkginfo(8), pkgmk(8), rejmerge(8) +.SH COPYRIGHT +pkgadd (pkgutils) is Copyright (c) 2000-2005 Per Liden and is licensed through +the GNU General Public License. Read the COPYING file for the complete license. diff --git a/pkgadd.cc b/pkgadd.cc new file mode 100644 index 0000000..dbc5641 --- /dev/null +++ b/pkgadd.cc @@ -0,0 +1,206 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +#include "pkgadd.h" +#include +#include +#include +#include +#include + +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 package = pkg_open(o_package); + vector 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 conflicting_files = db_find_conflicts(package.first, package.second); + + if (!conflicting_files.empty()) { + if (o_force) { + set 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(cerr, "\n")); + throw runtime_error("listed file(s) already installed (use -f to ignore and overwrite)"); + } + } + + set 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] " << endl + << "options:" << endl + << " -u, --upgrade upgrade package with the same name" << endl + << " -f, --force force install, overwrite conflicting files" << endl + << " -r, --root specify alternative installation root" << endl + << " -v, --version print version and exit" << endl + << " -h, --help print help and exit" << endl; +} + +vector pkgadd::read_config() const +{ + vector 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::const_iterator j = rules.begin(); j != rules.end(); j++) { + cerr << "\t" << (*j).pattern << "\t" << (*j).action << endl; + } + cerr << endl; +#endif + + return rules; +} + +set pkgadd::make_keep_list(const set& files, const vector& rules) const +{ + set keep_list; + + for (set::const_iterator i = files.begin(); i != files.end(); i++) { + for (vector::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::const_iterator j = keep_list.begin(); j != keep_list.end(); j++) { + cerr << " " << (*j) << endl; + } + cerr << endl; +#endif + + return keep_list; +} diff --git a/pkgadd.conf b/pkgadd.conf new file mode 100644 index 0000000..642e09b --- /dev/null +++ b/pkgadd.conf @@ -0,0 +1,21 @@ +# +# /etc/pkgadd.conf: pkgadd(8) configuration +# + +# Default rule (implicit) +#UPGRADE ^.*$ YES + +UPGRADE ^etc/.*$ NO +UPGRADE ^var/log/.*$ NO + +UPGRADE ^etc/mail/cf/.*$ YES +UPGRADE ^etc/ports/drivers/.*$ YES +UPGRADE ^etc/X11/.*$ YES + +UPGRADE ^etc/rc.*$ YES +UPGRADE ^etc/rc\.local$ NO +UPGRADE ^etc/rc\.modules$ NO +UPGRADE ^etc/rc\.conf$ NO +UPGRADE ^etc/rc\.d/net$ NO + +# End of file diff --git a/pkgadd.h b/pkgadd.h new file mode 100644 index 0000000..46e0f1b --- /dev/null +++ b/pkgadd.h @@ -0,0 +1,49 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +#ifndef PKGADD_H +#define PKGADD_H + +#include "pkgutil.h" +#include +#include + +#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 read_config() const; + set make_keep_list(const set& files, const vector& rules) const; +}; + +#endif /* PKGADD_H */ diff --git a/pkginfo.8.in b/pkginfo.8.in new file mode 100644 index 0000000..e5aba54 --- /dev/null +++ b/pkginfo.8.in @@ -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 " +List files owned by the specified or contained in . +.TP +.B "\-o, \-\-owner " +List owner(s) of file(s) matching . +.TP +.B "\-f, \-\-footprint " +Print footprint for . This feature is mainly used by pkgmk(8) +for creating and comparing footprints. +.TP +.B "\-r, \-\-root " +Specify alternative installation root (default is "/"). This +should be used if you want to display information about a package +that is installed on a temporary mounted partition, which is "owned" +by another system. By using this option you specify which package +database to use. +.TP +.B "\-v, \-\-version" +Print version and exit. +.TP +.B "\-h, \-\-help" +Print help and exit. +.SH SEE ALSO +pkgadd(8), pkgrm(8), pkgmk(8), rejmerge(8) +.SH COPYRIGHT +pkginfo (pkgutils) is Copyright (c) 2000-2005 Per Liden and is licensed through +the GNU General Public License. Read the COPYING file for the complete license. diff --git a/pkginfo.cc b/pkginfo.cc new file mode 100644 index 0000000..76980a3 --- /dev/null +++ b/pkginfo.cc @@ -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 +#include +#include +#include +#include + +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(cout, "\n")); + } else if (file_exists(o_arg)) { + pair package = pkg_open(o_arg); + copy(package.second.files.begin(), package.second.files.end(), ostream_iterator(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 > result; + result.push_back(pair("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::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(i->first, *j)); + if (i->first.length() > width) + width = i->first.length(); + } + } + } + + regfree(&preg); + + if (result.size() > 1) { + for (vector >::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 list files in or " << endl + << " -o, --owner list owner(s) of file(s) matching " << endl + << " -f, --footprint print footprint for " << endl + << " -r, --root specify alternative installation root" << endl + << " -v, --version print version and exit" << endl + << " -h, --help print help and exit" << endl; +} diff --git a/pkginfo.h b/pkginfo.h new file mode 100644 index 0000000..25a4686 --- /dev/null +++ b/pkginfo.h @@ -0,0 +1,34 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +#ifndef PKGINFO_H +#define PKGINFO_H + +#include "pkgutil.h" + +class pkginfo : public pkgutil { +public: + pkginfo() : pkgutil("pkginfo") {} + virtual void run(int argc, char** argv); + virtual void print_help() const; +}; + +#endif /* PKGINFO_H */ diff --git a/pkgmk.8.in b/pkgmk.8.in new file mode 100644 index 0000000..0be8d3a --- /dev/null +++ b/pkgmk.8.in @@ -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 " +Use alternative configuration file (default is /etc/pkgmk.conf). +.TP +.B "\-v, \-\-version" +Print version and exit. +.TP +.B "\-h, \-\-help" +Print help and exit. +.SH FILES +.TP +.B "Pkgfile" +Package build description. +.TP +.B ".footprint" +Package footprint (used for regression testing). +.TP +.B ".md5sum" +MD5 checksum of source files. +.TP +.B "/etc/pkgmk.conf" +Global package make configuration. +.TP +.B "wget" +Used by pkgmk to download source code. +.SH SEE ALSO +pkgadd(8), pkgrm(8), pkginfo(8), rejmerge(8), wget(1) +.SH COPYRIGHT +pkgmk (pkgutils) is Copyright (c) 2000-2005 Per Liden and is licensed through +the GNU General Public License. Read the COPYING file for the complete license. diff --git a/pkgmk.conf b/pkgmk.conf new file mode 100644 index 0000000..6e70f1b --- /dev/null +++ b/pkgmk.conf @@ -0,0 +1,15 @@ +# +# /etc/pkgmk.conf: pkgmk(8) configuration +# + +export CFLAGS="-O2 -march=i686 -pipe" +export CXXFLAGS="-O2 -march=i686 -pipe" + +# PKGMK_SOURCE_DIR="$PWD" +# PKGMK_PACKAGE_DIR="$PWD" +# PKGMK_WORK_DIR="$PWD/work" +# PKGMK_DOWNLOAD="no" +# PKGMK_IGNORE_FOOTPRINT="no" +# PKGMK_NO_STRIP="no" + +# End of file diff --git a/pkgmk.in b/pkgmk.in new file mode 100755 index 0000000..62a7418 --- /dev/null +++ b/pkgmk.in @@ -0,0 +1,653 @@ +#!/bin/bash +# +# pkgutils +# +# Copyright (c) 2000-2005 Per Liden +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +# USA. +# + +info() { + echo "=======> $1" +} + +warning() { + info "WARNING: $1" +} + +error() { + info "ERROR: $1" +} + +get_filename() { + local FILE="`echo $1 | sed 's|^.*://.*/||g'`" + + if [ "$FILE" != "$1" ]; then + FILE="$PKGMK_SOURCE_DIR/$FILE" + fi + + echo $FILE +} + +check_pkgfile() { + if [ ! "$name" ]; then + error "Variable 'name' not specified in $PKGMK_PKGFILE." + exit 1 + elif [ ! "$version" ]; then + error "Variable 'version' not specified in $PKGMK_PKGFILE." + exit 1 + elif [ ! "$release" ]; then + error "Variable 'release' not specified in $PKGMK_PKGFILE." + exit 1 + elif [ "`type -t build`" != "function" ]; then + error "Function 'build' not specified in $PKGMK_PKGFILE." + exit 1 + fi +} + +check_directory() { + if [ ! -d $1 ]; then + error "Directory '$1' does not exist." + exit 1 + elif [ ! -w $1 ]; then + error "Directory '$1' not writable." + exit 1 + elif [ ! -x $1 ] || [ ! -r $1 ]; then + error "Directory '$1' not readable." + exit 1 + fi +} + +download_file() { + info "Downloading '$1'." + + if [ ! "`type -p wget`" ]; then + error "Command 'wget' not found." + exit 1 + fi + + LOCAL_FILENAME=`get_filename $1` + LOCAL_FILENAME_PARTIAL="$LOCAL_FILENAME.partial" + DOWNLOAD_CMD="--passive-ftp --no-directories --tries=3 --waitretry=3 \ + --directory-prefix=$PKGMK_SOURCE_DIR --output-document=$LOCAL_FILENAME_PARTIAL $1" + + if [ -f "$LOCAL_FILENAME_PARTIAL" ]; then + info "Partial download found, trying to resume" + RESUME_CMD="-c" + fi + + while true; do + wget $RESUME_CMD $DOWNLOAD_CMD + error=$? + if [ $error != 0 ] && [ "$RESUME_CMD" ]; then + info "Partial download failed, restarting" + rm -f "$LOCAL_FILENAME_PARTIAL" + RESUME_CMD="" + else + break + fi + done + + if [ $error != 0 ]; then + error "Downloading '$1' failed." + exit 1 + fi + + mv -f "$LOCAL_FILENAME_PARTIAL" "$LOCAL_FILENAME" +} + +download_source() { + local FILE LOCAL_FILENAME + + for FILE in ${source[@]}; do + LOCAL_FILENAME=`get_filename $FILE` + if [ ! -e $LOCAL_FILENAME ]; then + if [ "$LOCAL_FILENAME" = "$FILE" ]; then + error "Source file '$LOCAL_FILENAME' not found (can not be downloaded, URL not specified)." + exit 1 + else + if [ "$PKGMK_DOWNLOAD" = "yes" ]; then + download_file $FILE + else + error "Source file '$LOCAL_FILENAME' not found (use option -d to download)." + exit 1 + fi + fi + fi + done +} + +unpack_source() { + local FILE LOCAL_FILENAME COMMAND + + for FILE in ${source[@]}; do + LOCAL_FILENAME=`get_filename $FILE` + case $LOCAL_FILENAME in + *.tar.gz|*.tar.Z|*.tgz) + COMMAND="tar -C $SRC --use-compress-program=gzip -xf $LOCAL_FILENAME" ;; + *.tar.bz2) + COMMAND="tar -C $SRC --use-compress-program=bzip2 -xf $LOCAL_FILENAME" ;; + *.zip) + COMMAND="unzip -qq -o -d $SRC $LOCAL_FILENAME" ;; + *) + COMMAND="cp $LOCAL_FILENAME $SRC" ;; + esac + + echo "$COMMAND" + + $COMMAND + + if [ $? != 0 ]; then + if [ "$PKGMK_KEEP_WORK" = "no" ]; then + rm -rf $PKGMK_WORK_DIR + fi + error "Building '$TARGET' failed." + exit 1 + fi + done +} + +make_md5sum() { + local FILE LOCAL_FILENAMES + + if [ "$source" ]; then + for FILE in ${source[@]}; do + LOCAL_FILENAMES="$LOCAL_FILENAMES `get_filename $FILE`" + done + + md5sum $LOCAL_FILENAMES | sed -e 's| .*/| |' | sort -k 2 + fi +} + +make_footprint() { + pkginfo --footprint $TARGET | \ + sed "s|\tlib/modules/`uname -r`/|\tlib/modules//|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 use alternative configuration file" + echo " -v, --version print version and exit " + echo " -h, --help print help and exit" +} + +parse_options() { + while [ "$1" ]; do + case $1 in + -i|--install) + PKGMK_INSTALL="install" ;; + -u|--upgrade) + PKGMK_INSTALL="upgrade" ;; + -r|--recursive) + PKGMK_RECURSIVE="yes" ;; + -d|--download) + PKGMK_DOWNLOAD="yes" ;; + -do|--download-only) + PKGMK_DOWNLOAD="yes" + PKGMK_DOWNLOAD_ONLY="yes" ;; + -utd|--up-to-date) + PKGMK_UP_TO_DATE="yes" ;; + -uf|--update-footprint) + PKGMK_UPDATE_FOOTPRINT="yes" ;; + -if|--ignore-footprint) + PKGMK_IGNORE_FOOTPRINT="yes" ;; + -um|--update-md5sum) + PKGMK_UPDATE_MD5SUM="yes" ;; + -im|--ignore-md5sum) + PKGMK_IGNORE_MD5SUM="yes" ;; + -cm|--check-md5sum) + PKGMK_CHECK_MD5SUM="yes" ;; + -ns|--no-strip) + PKGMK_NO_STRIP="yes" ;; + -f|--force) + PKGMK_FORCE="yes" ;; + -c|--clean) + PKGMK_CLEAN="yes" ;; + -kw|--keep-work) + PKGMK_KEEP_WORK="yes" ;; + -cf|--config-file) + if [ ! "$2" ]; then + echo "`basename $PKGMK_COMMAND`: option $1 requires an argument" + exit 1 + fi + PKGMK_CONFFILE="$2" + shift ;; + -v|--version) + echo "`basename $PKGMK_COMMAND` (pkgutils) $PKGMK_VERSION" + exit 0 ;; + -h|--help) + print_help + exit 0 ;; + *) + echo "`basename $PKGMK_COMMAND`: invalid option $1" + exit 1 ;; + esac + shift + done +} + +main() { + local FILE TARGET + + parse_options "$@" + + if [ "$PKGMK_RECURSIVE" = "yes" ]; then + recursive "$@" + exit 0 + fi + + for FILE in $PKGMK_PKGFILE $PKGMK_CONFFILE; do + if [ ! -f $FILE ]; then + error "File '$FILE' not found." + exit 1 + fi + . $FILE + done + + check_directory "$PKGMK_SOURCE_DIR" + check_directory "$PKGMK_PACKAGE_DIR" + check_directory "`dirname $PKGMK_WORK_DIR`" + + check_pkgfile + + TARGET="$PKGMK_PACKAGE_DIR/$name#$version-$release.pkg.tar.gz" + + if [ "$PKGMK_CLEAN" = "yes" ]; then + clean + exit 0 + fi + + if [ "$PKGMK_UPDATE_FOOTPRINT" = "yes" ]; then + update_footprint + exit 0 + fi + + if [ "$PKGMK_UPDATE_MD5SUM" = "yes" ]; then + download_source + make_md5sum > $PKGMK_MD5SUM + info "Md5sum updated." + exit 0 + fi + + if [ "$PKGMK_DOWNLOAD_ONLY" = "yes" ]; then + download_source + exit 0 + fi + + if [ "$PKGMK_UP_TO_DATE" = "yes" ]; then + if [ "`build_needed`" = "yes" ]; then + info "Package '$TARGET' is not up to date." + else + info "Package '$TARGET' is up to date." + fi + exit 0 + fi + + if [ "`build_needed`" = "no" ] && [ "$PKGMK_FORCE" = "no" ] && [ "$PKGMK_CHECK_MD5SUM" = "no" ]; then + info "Package '$TARGET' is up to date." + else + download_source + build_package + fi + + if [ "$PKGMK_INSTALL" != "no" ]; then + install_package + fi + + exit 0 +} + +trap "interrupted" SIGHUP SIGINT SIGQUIT SIGTERM + +export LC_ALL=POSIX + +readonly PKGMK_VERSION="#VERSION#" +readonly PKGMK_COMMAND="$0" +readonly PKGMK_ROOT="$PWD" + +PKGMK_CONFFILE="/etc/pkgmk.conf" +PKGMK_PKGFILE="Pkgfile" +PKGMK_FOOTPRINT=".footprint" +PKGMK_MD5SUM=".md5sum" +PKGMK_NOSTRIP=".nostrip" + +PKGMK_SOURCE_DIR="$PWD" +PKGMK_PACKAGE_DIR="$PWD" +PKGMK_WORK_DIR="$PWD/work" + +PKGMK_INSTALL="no" +PKGMK_RECURSIVE="no" +PKGMK_DOWNLOAD="no" +PKGMK_DOWNLOAD_ONLY="no" +PKGMK_UP_TO_DATE="no" +PKGMK_UPDATE_FOOTPRINT="no" +PKGMK_IGNORE_FOOTPRINT="no" +PKGMK_FORCE="no" +PKGMK_KEEP_WORK="no" +PKGMK_UPDATE_MD5SUM="no" +PKGMK_IGNORE_MD5SUM="no" +PKGMK_CHECK_MD5SUM="no" +PKGMK_NO_STRIP="no" +PKGMK_CLEAN="no" + +main "$@" + +# End of file diff --git a/pkgrm.8.in b/pkgrm.8.in new file mode 100644 index 0000000..e7b46d2 --- /dev/null +++ b/pkgrm.8.in @@ -0,0 +1,27 @@ +.TH pkgrm 8 "" "pkgutils #VERSION#" "" +.SH NAME +pkgrm \- remove software package +.SH SYNOPSIS +\fBpkgrm [options] \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 " +Specify alternative installation root (default is "/"). This +should be used if you want to remove a package from a temporary +mounted partition, which is "owned" by another system. By using +this option you not only specify where the software is installed, +but you also specify which package database to use. +.TP +.B "\-v, \-\-version" +Print version and exit. +.TP +.B "\-h, \-\-help" +Print help and exit. +.SH SEE ALSO +pkgadd(8), pkginfo(8), pkgmk(8), rejmerge(8) +.SH COPYRIGHT +pkgrm (pkgutils) is Copyright (c) 2000-2005 Per Liden and is licensed through +the GNU General Public License. Read the COPYING file for the complete license. diff --git a/pkgrm.cc b/pkgrm.cc new file mode 100644 index 0000000..a779f71 --- /dev/null +++ b/pkgrm.cc @@ -0,0 +1,78 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +#include "pkgrm.h" +#include + +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] " << endl + << "options:" << endl + << " -r, --root specify alternative installation root" << endl + << " -v, --version print version and exit" << endl + << " -h, --help print help and exit" << endl; +} diff --git a/pkgrm.h b/pkgrm.h new file mode 100644 index 0000000..3958367 --- /dev/null +++ b/pkgrm.h @@ -0,0 +1,34 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +#ifndef PKGRM_H +#define PKGRM_H + +#include "pkgutil.h" + +class pkgrm : public pkgutil { +public: + pkgrm() : pkgutil("pkgrm") {} + virtual void run(int argc, char** argv); + virtual void print_help() const; +}; + +#endif /* PKGRM_H */ diff --git a/pkgutil.cc b/pkgutil.cc new file mode 100644 index 0000000..571dd6e --- /dev/null +++ b/pkgutil.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 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(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 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(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::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(cerr, "\n")); + cerr << endl; +#endif + + // Delete the files + for (set::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& keep_list) +{ + set 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(cerr, "\n")); + cerr << endl; +#endif + + // Don't delete files found in the keep list + for (set::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(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::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(cerr, "\n")); + cerr << endl; +#endif + + // Delete the files + for (set::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 files, const set& keep_list) +{ + // Remove all references + for (packages_t::iterator i = packages.begin(); i != packages.end(); ++i) + for (set::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(cerr, "\n")); + cerr << endl; +#endif + + // Don't delete files found in the keep list + for (set::const_iterator i = keep_list.begin(); i != keep_list.end(); ++i) + files.erase(*i); + + // Delete the files + for (set::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 pkgutil::db_find_conflicts(const string& name, const pkginfo_t& info) +{ + set 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(cerr, "\n")); + cerr << endl; +#endif + + // Find conflicting files in filesystem + for (set::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(cerr, "\n")); + cerr << endl; +#endif + + // Exclude directories + set tmp = files; + for (set::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(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::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(cerr, "\n")); + cerr << endl; +#endif + } + + return files; +} + +pair pkgutil::pkg_open(const string& filename) const +{ + pair 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(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& keep_list) const +{ + TAR* t; + unsigned int i; + + if (tar_open(&t, const_cast(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(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(filename.c_str()), &gztype, O_RDONLY, 0, TAR_GNU) == -1) + throw runtime_error_with_errno("could not open " + filename); + + for (i = 0; !th_read(t); ++i) { + // Access permissions + if (TH_ISSYM(t)) { + // Access permissions on symlinks differ among filesystems, e.g. XFS and ext2 have different. + // To avoid getting different footprints we always use "lrwxrwxrwx". + cout << "lrwxrwxrwx"; + } else { + cout << mtos(th_get_mode(t)); + } + + cout << '\t'; + + // User + uid_t uid = th_get_uid(t); + struct passwd* pw = getpwuid(uid); + if (pw) + cout << pw->pw_name; + else + cout << uid; + + cout << '/'; + + // Group + gid_t gid = th_get_gid(t); + struct group* gr = getgrgid(gid); + if (gr) + cout << gr->gr_name; + else + cout << gid; + + // Filename + cout << '\t' << th_get_pathname(t); + + // Special cases + if (TH_ISSYM(t)) { + // Symlink + cout << " -> " << th_get_linkname(t); + } else if (TH_ISCHR(t) || TH_ISBLK(t)) { + // Device + cout << " (" << th_get_devmajor(t) << ", " << th_get_devminor(t) << ")"; + } else if (TH_ISREG(t) && !th_get_size(t)) { + // Empty regular file + cout << " (EMPTY)"; + } + + cout << '\n'; + + if (TH_ISREG(t) && tar_skip_regfile(t)) + throw runtime_error_with_errno("could not read " + filename); + } + + if (i == 0) { + if (errno == 0) + throw runtime_error("empty package"); + else + throw runtime_error("could not read " + filename); + } + + tar_close(t); +} + +void pkgutil::print_version() const +{ + cout << utilname << " (pkgutils) " << VERSION << endl; +} + +db_lock::db_lock(const string& root, bool exclusive) + : dir(0) +{ + const string dirname = trim_filename(root + string("/") + PKG_DIR); + + if (!(dir = opendir(dirname.c_str()))) + throw runtime_error_with_errno("could not read directory " + dirname); + + if (flock(dirfd(dir), (exclusive ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) { + if (errno == EWOULDBLOCK) + throw runtime_error("package database is currently locked by another process"); + else + throw runtime_error_with_errno("could not lock directory " + dirname); + } +} + +db_lock::~db_lock() +{ + if (dir) { + flock(dirfd(dir), LOCK_UN); + closedir(dir); + } +} + +void assert_argument(char** argv, int argc, int index) +{ + if (argc - 1 < index + 1) + throw runtime_error("option " + string(argv[index]) + " requires an argument"); +} + +string itos(unsigned int value) +{ + static char buf[20]; + sprintf(buf, "%u", value); + return buf; +} + +string mtos(mode_t mode) +{ + string s; + + // File type + switch (mode & S_IFMT) { + case S_IFREG: s += '-'; break; // Regular + case S_IFDIR: s += 'd'; break; // Directory + case S_IFLNK: s += 'l'; break; // Symbolic link + case S_IFCHR: s += 'c'; break; // Character special + case S_IFBLK: s += 'b'; break; // Block special + case S_IFSOCK: s += 's'; break; // Socket + case S_IFIFO: s += 'p'; break; // Fifo + default: s += '?'; break; // Unknown + } + + // User permissions + s += (mode & S_IRUSR) ? 'r' : '-'; + s += (mode & S_IWUSR) ? 'w' : '-'; + switch (mode & (S_IXUSR | S_ISUID)) { + case S_IXUSR: s += 'x'; break; + case S_ISUID: s += 'S'; break; + case S_IXUSR | S_ISUID: s += 's'; break; + default: s += '-'; break; + } + + // Group permissions + s += (mode & S_IRGRP) ? 'r' : '-'; + s += (mode & S_IWGRP) ? 'w' : '-'; + switch (mode & (S_IXGRP | S_ISGID)) { + case S_IXGRP: s += 'x'; break; + case S_ISGID: s += 'S'; break; + case S_IXGRP | S_ISGID: s += 's'; break; + default: s += '-'; break; + } + + // Other permissions + s += (mode & S_IROTH) ? 'r' : '-'; + s += (mode & S_IWOTH) ? 'w' : '-'; + switch (mode & (S_IXOTH | S_ISVTX)) { + case S_IXOTH: s += 'x'; break; + case S_ISVTX: s += 'T'; break; + case S_IXOTH | S_ISVTX: s += 't'; break; + default: s += '-'; break; + } + + return s; +} + +int unistd_gzopen(char* pathname, int flags, mode_t mode) +{ + char* gz_mode; + + switch (flags & O_ACCMODE) { + case O_WRONLY: + gz_mode = "w"; + break; + + case O_RDONLY: + gz_mode = "r"; + break; + + case O_RDWR: + default: + errno = EINVAL; + return -1; + } + + int fd; + gzFile gz_file; + + if ((fd = open(pathname, flags, mode)) == -1) + return -1; + + if ((flags & O_CREAT) && fchmod(fd, mode)) + return -1; + + if (!(gz_file = gzdopen(fd, gz_mode))) { + errno = ENOMEM; + return -1; + } + + return (int)gz_file; +} + +string trim_filename(const string& filename) +{ + string search("//"); + string result = filename; + + for (string::size_type pos = result.find(search); pos != string::npos; pos = result.find(search)) + result.replace(pos, search.size(), "/"); + + return result; +} + +bool file_exists(const string& filename) +{ + struct stat buf; + return !lstat(filename.c_str(), &buf); +} + +bool file_empty(const string& filename) +{ + struct stat buf; + + if (lstat(filename.c_str(), &buf) == -1) + return false; + + return (S_ISREG(buf.st_mode) && buf.st_size == 0); +} + +bool file_equal(const string& file1, const string& file2) +{ + struct stat buf1, buf2; + + if (lstat(file1.c_str(), &buf1) == -1) + return false; + + if (lstat(file2.c_str(), &buf2) == -1) + return false; + + // Regular files + if (S_ISREG(buf1.st_mode) && S_ISREG(buf2.st_mode)) { + ifstream f1(file1.c_str()); + ifstream f2(file2.c_str()); + + if (!f1 || !f2) + return false; + + while (!f1.eof()) { + char buffer1[4096]; + char buffer2[4096]; + f1.read(buffer1, 4096); + f2.read(buffer2, 4096); + if (f1.gcount() != f2.gcount() || + memcmp(buffer1, buffer2, f1.gcount()) || + f1.eof() != f2.eof()) + return false; + } + + return true; + } + // Symlinks + else if (S_ISLNK(buf1.st_mode) && S_ISLNK(buf2.st_mode)) { + char symlink1[MAXPATHLEN]; + char symlink2[MAXPATHLEN]; + + memset(symlink1, 0, MAXPATHLEN); + memset(symlink2, 0, MAXPATHLEN); + + if (readlink(file1.c_str(), symlink1, MAXPATHLEN - 1) == -1) + return false; + + if (readlink(file2.c_str(), symlink2, MAXPATHLEN - 1) == -1) + return false; + + return !strncmp(symlink1, symlink2, MAXPATHLEN); + } + // Character devices + else if (S_ISCHR(buf1.st_mode) && S_ISCHR(buf2.st_mode)) { + return buf1.st_dev == buf2.st_dev; + } + // Block devices + else if (S_ISBLK(buf1.st_mode) && S_ISBLK(buf2.st_mode)) { + return buf1.st_dev == buf2.st_dev; + } + + return false; +} + +bool permissions_equal(const string& file1, const string& file2) +{ + struct stat buf1; + struct stat buf2; + + if (lstat(file1.c_str(), &buf1) == -1) + return false; + + if (lstat(file2.c_str(), &buf2) == -1) + return false; + + return(buf1.st_mode == buf2.st_mode) && + (buf1.st_uid == buf2.st_uid) && + (buf1.st_gid == buf2.st_gid); +} + +void file_remove(const string& basedir, const string& filename) +{ + if (filename != basedir && !remove(filename.c_str())) { + char* path = strdup(filename.c_str()); + file_remove(basedir, dirname(path)); + free(path); + } +} diff --git a/pkgutil.h b/pkgutil.h new file mode 100644 index 0000000..9806900 --- /dev/null +++ b/pkgutil.h @@ -0,0 +1,108 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +#ifndef PKGUTIL_H +#define PKGUTIL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 files; + }; + + typedef map 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& keep_list); + void db_rm_files(set files, const set& keep_list); + set db_find_conflicts(const string& name, const pkginfo_t& info); + + // Tar.gz + pair pkg_open(const string& filename) const; + void pkg_install(const string& filename, const set& keep_list) const; + void pkg_footprint(string& filename) const; + void ldconfig() const; + + string utilname; + packages_t packages; + string root; +}; + +class db_lock { +public: + db_lock(const string& root, bool exclusive); + ~db_lock(); +private: + DIR* dir; +}; + +class runtime_error_with_errno : public runtime_error { +public: + explicit runtime_error_with_errno(const string& msg) throw() + : runtime_error(msg + string(": ") + strerror(errno)) {} +}; + +// Utility functions +void assert_argument(char** argv, int argc, int index); +string itos(unsigned int value); +string mtos(mode_t mode); +int unistd_gzopen(char* pathname, int flags, mode_t mode); +string trim_filename(const string& filename); +bool file_exists(const string& filename); +bool file_empty(const string& filename); +bool file_equal(const string& file1, const string& file2); +bool permissions_equal(const string& file1, const string& file2); +void file_remove(const string& basedir, const string& filename); + +#endif /* PKGUTIL_H */ diff --git a/rejmerge.8.in b/rejmerge.8.in new file mode 100644 index 0000000..210d2c0 --- /dev/null +++ b/rejmerge.8.in @@ -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 " +Specify alternative root (default is "/"). This should be used +if you want to merge rejected files on a temporary mounted partition, +which is "owned" by another system. +.TP +.B "\-v, \-\-version" +Print version and exit. +.TP +.B "\-h, \-\-help" +Print help and exit. +.SH CONFIGURATION +When \fBrejmerge\fP is started it will source \fI/etc/rejmerge.conf\fP. +This file can be used to alter the way \fBrejmerge\fP displays file differences and performs file +merges. Changing the default behaviour is done by re-defining the shell functions \fBrejmerge_diff()\fP +and/or \fBrejmerge_merge()\fP. +.TP +.B rejmerge_diff() +This function is executed once for each rejected file. Arguments \fB$1\fP and \fB$2\fP contain the paths +to the installed and rejected files. Argument \fB$3\fP contains the path to a temporary file where this +function should write its result. The contents of the temporary file will later be presented to the user +as the difference between the two files. +.TP +.B rejmerge_merge() +This function is executed when the user chooses to merge two files. Arguments \fB$1\fP and \fB$2\fP +contain the paths to the installed and rejected files. Argument \fB$3\fP contains the path to a temporary +file where this function should write its result. The contents of the temporary file will later be +presented to the user as the merge result. +This function also has the option to set the variable \fB$REJMERGE_MERGE_INFO\fP. The contents of this +variable will be displayed as informational text after a merge has been performed. Its purpose is to provide +information about the merge, e.g. "5 merge conflicts found". + +.PP +Example: + +.nf +# +# /etc/rejmerge.conf: rejmerge(8) configuration +# + +rejmerge_diff() { + # Use diff(1) to produce side-by-side output + diff -y $1 $2 > $3 +} + +rejmerge_merge() { + # Use sdiff(1) to merge + sdiff -o $3 $1 $2 +} + +# End of file +.fi + +.SH FILES +.TP +.B "/etc/rejmerge.conf" +Configuration file. +.TP +.B "/var/lib/pkg/rejected/" +Directory where rejected files are stored. +.SH SEE ALSO +pkgadd(8), pkgrm(8), pkginfo(8), pkgmk(8) +.SH COPYRIGHT +rejmerge (pkgutils) is Copyright (c) 2000-2005 Per Liden and is licensed through +the GNU General Public License. Read the COPYING file for the complete license. diff --git a/rejmerge.conf b/rejmerge.conf new file mode 100644 index 0000000..c80af34 --- /dev/null +++ b/rejmerge.conf @@ -0,0 +1,5 @@ +# +# /etc/rejmerge.conf: rejmerge(8) configuration +# + +# End of file diff --git a/rejmerge.in b/rejmerge.in new file mode 100755 index 0000000..e95f4df --- /dev/null +++ b/rejmerge.in @@ -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 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