From: Jose V Beneyto Date: Tue, 8 Dec 2009 16:51:39 +0000 (+0100) Subject: Added original code from pkgutils 5.34.2 X-Git-Url: http://gitweb/?a=commitdiff_plain;h=a0a634132e76613b552d33b7f62d1706836d8b5d;p=pkgutils-cross.git Added original code from pkgutils 5.34.2 --- diff --git a/src/COPYING b/src/COPYING new file mode 100644 index 0000000..96e4591 --- /dev/null +++ b/src/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/src/Makefile b/src/Makefile new file mode 100644 index 0000000..36f11f4 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,101 @@ +# +# pkgutils +# +# Copyright (c) 2000-2005 by Per Liden +# Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +# +# 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.34.2 +NAME = pkgutils-$(VERSION) + +CXXFLAGS += -DNDEBUG +CXXFLAGS += -O2 -Wall -pedantic -D_GNU_SOURCE -DVERSION=\"$(VERSION)\" \ + -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 + +LDFLAGS += -static -larchive -lz + +OBJECTS = main.o pkgutil.o pkgadd.o pkgrm.o pkginfo.o + +MANPAGES = pkgadd.8 pkgrm.8 pkginfo.8 pkgmk.8 rejmerge.8 pkgmk.conf.5 + +all: pkgadd pkgmk rejmerge man + +pkgadd: .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 $(NAME) $(NAME).tar.gz + git archive --format=tar --prefix=$(NAME)/ HEAD | tar -x + git log > $(NAME)/ChangeLog + tar czvf $(NAME).tar.gz $(NAME) + rm -rf $(NAME) + +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 + install -D -m0644 pkgmk.conf.5 $(DESTDIR)$(MANDIR)/man5/pkgmk.conf.5 + 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 + +# End of file diff --git a/src/README b/src/README new file mode 100644 index 0000000..1e57cdc --- /dev/null +++ b/src/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 +Copyright (c) 2006-2007 CRUX team (http://crux.nu). + +pkgutils is licensed through the GNU General Public License. +Read the COPYING file for the complete license. diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..566f4db --- /dev/null +++ b/src/main.cc @@ -0,0 +1,77 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +// +// 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/src/pkgadd.8.in b/src/pkgadd.8.in new file mode 100644 index 0000000..a4747ca --- /dev/null +++ b/src/pkgadd.8.in @@ -0,0 +1,63 @@ +.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 +\fBpkgadd\fP is configured by the file \fI/etc/pkgadd.conf\fP. This file can contain rules, that are built out of three fragments: \fIevent\fP, \fIpattern\fP and \fIaction\fP. The event describes in what kind of situation this rule applies. Currently there are two types of events: \fBUPGRADE\fP and \fBINSTALL\fP. \fBUPGRADE\fP rules are applied when a package is installed over an existing version, and \fBINSTALL\fP rules are applied in any case. The pattern is a regular expression. The action applicable to both the \fBUPGRADE\fP and \fBINSTALL\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/install 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 Copyright (c) 2006-2007 CRUX team (http://crux.nu). +pkgadd (pkgutils) is licensed through the GNU General Public License. +Read the COPYING file for the complete license. diff --git a/src/pkgadd.cc b/src/pkgadd.cc new file mode 100644 index 0000000..4bc1652 --- /dev/null +++ b/src/pkgadd.cc @@ -0,0 +1,270 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +// +// 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 non_install_files = apply_install_rules(package.first, package.second, config_rules); + 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, non_install_files); + 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") || !strcmp(event, "INSTALL")) { + rule_t rule; + rule.event = strcmp(event, "UPGRADE") ? INSTALL : 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; + vector found; + + find_rules(rules, UPGRADE, found); + + for (set::const_iterator i = files.begin(); i != files.end(); i++) { + for (vector::reverse_iterator j = found.rbegin(); j != found.rend(); j++) { + if (rule_applies_to_file(*j, *i)) { + if (!(*j).action) + keep_list.insert(keep_list.end(), *i); + + break; + } + } + } + +#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; +} + +set pkgadd::apply_install_rules(const string& name, pkginfo_t& info, const vector& rules) +{ + // TODO: better algo(?) + set install_set; + set non_install_set; + vector found; + + find_rules(rules, INSTALL, found); + + for (set::const_iterator i = info.files.begin(); i != info.files.end(); i++) { + bool install_file = true; + + for (vector::reverse_iterator j = found.rbegin(); j != found.rend(); j++) { + if (rule_applies_to_file(*j, *i)) { + install_file = (*j).action; + break; + } + } + + if (install_file) + install_set.insert(install_set.end(), *i); + else + non_install_set.insert(*i); + } + + info.files.clear(); + info.files = install_set; + +#ifndef NDEBUG + cerr << "Install set:" << endl; + for (set::iterator j = info.files.begin(); j != info.files.end(); j++) { + cerr << " " << (*j) << endl; + } + cerr << endl; + + cerr << "Non-Install set:" << endl; + for (set::iterator j = non_install_set.begin(); j != non_install_set.end(); j++) { + cerr << " " << (*j) << endl; + } + cerr << endl; +#endif + + return non_install_set; +} + +void pkgadd::find_rules(const vector& rules, rule_event_t event, vector& found) const +{ + for (vector::const_iterator i = rules.begin(); i != rules.end(); i++) + if (i->event == event) + found.push_back(*i); +} + +bool pkgadd::rule_applies_to_file(const rule_t& rule, const string& file) const +{ + regex_t preg; + bool ret; + + if (regcomp(&preg, rule.pattern.c_str(), REG_EXTENDED | REG_NOSUB)) + throw runtime_error("error compiling regular expression '" + rule.pattern + "', aborting"); + + ret = !regexec(&preg, file.c_str(), 0, 0, 0); + regfree(&preg); + + return ret; +} diff --git a/src/pkgadd.conf b/src/pkgadd.conf new file mode 100644 index 0000000..6af9cb8 --- /dev/null +++ b/src/pkgadd.conf @@ -0,0 +1,25 @@ +# +# /etc/pkgadd.conf: pkgadd(8) configuration +# + +# Default rule (implicit) +#UPGRADE ^.*$ YES + +UPGRADE ^etc/.*$ NO +UPGRADE ^var/log/.*$ NO +UPGRADE ^var/spool/\w*cron/.*$ NO +UPGRADE ^var/run/utmp$ NO + +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 + +UPGRADE ^etc/udev/rules.d/.*$ YES +UPGRADE ^etc/udev/rules.d/1.*$ NO + +# End of file diff --git a/src/pkgadd.h b/src/pkgadd.h new file mode 100644 index 0000000..e121dc1 --- /dev/null +++ b/src/pkgadd.h @@ -0,0 +1,58 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +// +// 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 + +enum rule_event_t { + UPGRADE, + INSTALL +}; + +struct rule_t { + rule_event_t 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; + set apply_install_rules(const string& name, pkginfo_t& info, const vector& rules); + void find_rules(const vector& rules, rule_event_t event, vector& found) const; + bool rule_applies_to_file(const rule_t& rule, const string& file) const; +}; + +#endif /* PKGADD_H */ diff --git a/src/pkginfo.8.in b/src/pkginfo.8.in new file mode 100644 index 0000000..8d2f825 --- /dev/null +++ b/src/pkginfo.8.in @@ -0,0 +1,42 @@ +.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 Copyright (c) 2006-2007 CRUX team (http://crux.nu). +pkginfo (pkgutils) is licensed through the GNU General Public License. +Read the COPYING file for the complete license. diff --git a/src/pkginfo.cc b/src/pkginfo.cc new file mode 100644 index 0000000..d4b8270 --- /dev/null +++ b/src/pkginfo.cc @@ -0,0 +1,155 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +// +// 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/src/pkginfo.h b/src/pkginfo.h new file mode 100644 index 0000000..535dfbe --- /dev/null +++ b/src/pkginfo.h @@ -0,0 +1,35 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +// +// 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/src/pkgmk.8.in b/src/pkgmk.8.in new file mode 100644 index 0000000..b9fca26 --- /dev/null +++ b/src/pkgmk.8.in @@ -0,0 +1,93 @@ +.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 +pkgmk.conf(5), pkgadd(8), pkgrm(8), pkginfo(8), rejmerge(8), wget(1) +.SH COPYRIGHT +pkgmk (pkgutils) is Copyright (c) 2000-2005 Per Liden and Copyright (c) 2006-2007 CRUX team (http://crux.nu). +pkgmk (pkgutils) is licensed through the GNU General Public License. +Read the COPYING file for the complete license. diff --git a/src/pkgmk.conf b/src/pkgmk.conf new file mode 100644 index 0000000..07b1cbd --- /dev/null +++ b/src/pkgmk.conf @@ -0,0 +1,17 @@ +# +# /etc/pkgmk.conf: pkgmk(8) configuration +# + +export CFLAGS="-O2 -march=i686 -pipe" +export CXXFLAGS="-O2 -march=i686 -pipe" + +# PKGMK_SOURCE_MIRRORS=() +# PKGMK_SOURCE_DIR="$PWD" +# PKGMK_PACKAGE_DIR="$PWD" +# PKGMK_WORK_DIR="$PWD/work" +# PKGMK_DOWNLOAD="no" +# PKGMK_IGNORE_FOOTPRINT="no" +# PKGMK_NO_STRIP="no" +# PKGMK_WGET_OPTS="" + +# End of file diff --git a/src/pkgmk.conf.5.in b/src/pkgmk.conf.5.in new file mode 100644 index 0000000..dcb6a20 --- /dev/null +++ b/src/pkgmk.conf.5.in @@ -0,0 +1,65 @@ +.TH pkgmk.conf 5 "" "pkgutils #VERSION#" "" +.SH NAME +\fBpkgmk.conf\fP \- Configuration file for pkgmk. +.SH DESCRIPTION +\fBpkgmk.conf\fP configures pkgutils package make, pkgmk(8). +.SH FILE FORMAT +The file consists of a number of variable assignments of the form \fBoption\fP=\fBvalue\fP. Comments can be specified by putting a hash (#) symbol as the first character on the line. +.SH DIRECTIVES +.LP +If some option is not used (commented out or not included in the configuration file at all) pkgmk will take a default action. +.TP +\fBexport CFLAGS='STRING'\fP +Set C compiler options. +.br +Default: none +.TP +\fBexport CXXFLAGS='STRING'\fP +Set C++ compiler options. +.br +Default: none +.TP +\fBPKGMK_SOURCE_MIRRORS=('STRING')\fP +Set mirrors to check and download source archives from. +.br +Default: none +.TP +\fBPKGMK_SOURCE_DIR='STRING'\fP +Set directory for downloaded source archives. +.br +Default: directory of Pkgfile. +.TP +\fBPKGMK_PACKAGE_DIR='STRING'\fR +Set directory for built packages. +.br +Default: directory of Pkgfile. +.TP +\fBPKGMK_WORK_DIR='STRING'\fP +Set directory for building packages. +.br +Default: '\fBfoo\fP/work', where \fBfoo\fP is the directory of the Pkgfile. +.TP +\fBPKGMK_WGET_OPTS='STRING'\fP +Additional options for wget(1), which is used by pkgmk to download all files. +.br +.TP +\fBPKGMK_DOWNLOAD='STRING'\fP +If set to 'yes', pkgmk will download the source archives if necessary. +.br +Default: 'no' +.TP +\fBPKGMK_IGNORE_FOOTPRINT='STRING'\fP +If set to 'yes', pkgmk will not perform a footprint check of the built package. +.br +Default: 'no' +.TP +\fBPKGMK_NO_STRIP='STRING'\fP +If set to 'no', pkgmk will strip built binaries. +.br +Default: 'no' +.SH SEE ALSO +pkgmk(8) +.SH COPYRIGHT +pkgmk (pkgutils) is Copyright (c) 2000-2005 Per Liden and Copyright (c) 2006-2008 CRUX team (http://crux.nu). +pkgmk (pkgutils) is licensed through the GNU General Public License. +Read the COPYING file for the complete license. diff --git a/src/pkgmk.in b/src/pkgmk.in new file mode 100755 index 0000000..c7f5432 --- /dev/null +++ b/src/pkgmk.in @@ -0,0 +1,703 @@ +#!/bin/bash +# +# pkgutils +# +# Copyright (c) 2000-2005 Per Liden +# Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +# +# 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" >&2 +} + +error() { + info "ERROR: $1" >&2 +} + +get_filename() { + if [[ $1 =~ ^(http|https|ftp|file)://.*/(.+) ]]; then + echo "$PKGMK_SOURCE_DIR/${BASH_REMATCH[2]}" + else + echo $1 + fi +} + +get_basename() { + local FILE="`echo $1 | sed 's|^.*://.*/||g'`" + 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 +} + +check_file() { + if [ -e $1 ] && [ ! -w $1 ]; then + error "File '$1' is not writable." + 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_OPTS="--passive-ftp --no-directories --tries=3 --waitretry=3 \ + --directory-prefix=$PKGMK_SOURCE_DIR \ + --output-document=$LOCAL_FILENAME_PARTIAL --no-check-certificate" + + if [ -f "$LOCAL_FILENAME_PARTIAL" ]; then + info "Partial download found, trying to resume" + RESUME_CMD="-c" + fi + + error=1 + + BASENAME=`get_basename $1` + for REPO in ${PKGMK_SOURCE_MIRRORS[@]}; do + REPO="`echo $REPO | sed 's|/$||'`" + wget $RESUME_CMD $DOWNLOAD_OPTS $PKGMK_WGET_OPTS $REPO/$BASENAME + error=$? + if [ $error == 0 ]; then + break + fi + done + + if [ $error != 0 ]; then + while true; do + wget $RESUME_CMD $DOWNLOAD_OPTS $PKGMK_WGET_OPTS $1 + error=$? + if [ $error != 0 ] && [ "$RESUME_CMD" ]; then + info "Partial download failed, restarting" + rm -f "$LOCAL_FILENAME_PARTIAL" + RESUME_CMD="" + else + break + fi + done + fi + + 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|*.tar.bz2|*.tar.xz|*.tar.lzma|*.zip) + COMMAND="bsdtar -p -o -C $SRC -xf $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 >&2 + + 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 + case $(file -b "$FILE") in + *ELF*executable*not\ stripped) + strip --strip-all "$FILE" + ;; + *ELF*shared\ object*not\ stripped) + strip --strip-unneeded "$FILE" + ;; + current\ ar\ archive) + strip --strip-debug "$FILE" + esac + 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 >&2 + 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 +} + +make_work_dir() { + export PKG="$PKGMK_WORK_DIR/pkg" + export SRC="$PKGMK_WORK_DIR/src" + umask 022 + + cd $PKGMK_ROOT + remove_work_dir + mkdir -p $SRC $PKG + + if [ "$PKGMK_IGNORE_MD5SUM" = "no" ]; then + check_md5sum + fi +} + +remove_work_dir() { + rm -rf $PKGMK_WORK_DIR +} + + +build_package() { + local BUILD_SUCCESSFUL="no" + + check_file "$TARGET" + make_work_dir + + 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 + remove_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 + + check_file "$PKGMK_FOOTPRINT" + 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 " -eo, --extract-only do not build, only extract 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" ;; + -eo|--extract-only) + PKGMK_EXTRACT_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 + check_file "$PKGMK_MD5SUM" + make_md5sum > $PKGMK_MD5SUM + info "Md5sum updated." + exit 0 + fi + + if [ "$PKGMK_DOWNLOAD_ONLY" = "yes" ]; then + download_source + exit 0 + fi + + if [ "$PKGMK_EXTRACT_ONLY" = "yes" ]; then + download_source + make_work_dir + info "Extracting sources of package '$name-$version'." + unpack_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_MIRRORS=() +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_EXTRACT_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/src/pkgrm.8.in b/src/pkgrm.8.in new file mode 100644 index 0000000..25de6a3 --- /dev/null +++ b/src/pkgrm.8.in @@ -0,0 +1,28 @@ +.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 Copyright (c) 2006-2007 CRUX team (http://crux.nu). +pkgrm (pkgutils) is licensed through the GNU General Public License. +Read the COPYING file for the complete license. diff --git a/src/pkgrm.cc b/src/pkgrm.cc new file mode 100644 index 0000000..18d6169 --- /dev/null +++ b/src/pkgrm.cc @@ -0,0 +1,79 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +// +// 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/src/pkgrm.h b/src/pkgrm.h new file mode 100644 index 0000000..5a5ebb0 --- /dev/null +++ b/src/pkgrm.h @@ -0,0 +1,35 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +// +// 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/src/pkgutil.cc b/src/pkgutil.cc new file mode 100644 index 0000000..69fefc1 --- /dev/null +++ b/src/pkgutil.cc @@ -0,0 +1,814 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +// +// 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 + +#define INIT_ARCHIVE(ar) \ + archive_read_support_compression_gzip((ar)); \ + archive_read_support_format_tar((ar)) + +using __gnu_cxx::stdio_filebuf; + +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; + struct archive* archive; + struct archive_entry* entry; + + // 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; + + archive = archive_read_new(); + INIT_ARCHIVE(archive); + + if (archive_read_open_filename(archive, + const_cast(filename.c_str()), + ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) + throw runtime_error_with_errno("could not open " + filename, archive_errno(archive)); + + for (i = 0; archive_read_next_header(archive, &entry) == + ARCHIVE_OK; ++i) { + + result.second.files.insert(result.second.files.end(), + archive_entry_pathname(entry)); + + mode_t mode = archive_entry_mode(entry); + + if (S_ISREG(mode) && + archive_read_data_skip(archive) != ARCHIVE_OK) + throw runtime_error_with_errno("could not read " + filename, archive_errno(archive)); + } + + if (i == 0) { + if (archive_errno(archive) == 0) + throw runtime_error("empty package"); + else + throw runtime_error("could not read " + filename); + } + + archive_read_finish(archive); + + return result; +} + +void pkgutil::pkg_install(const string& filename, const set& keep_list, const set& non_install_list) const +{ + struct archive* archive; + struct archive_entry* entry; + unsigned int i; + char buf[PATH_MAX]; + string absroot; + + archive = archive_read_new(); + INIT_ARCHIVE(archive); + + if (archive_read_open_filename(archive, + const_cast(filename.c_str()), + ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) + throw runtime_error_with_errno("could not open " + filename, archive_errno(archive)); + + chdir(root.c_str()); + absroot = getcwd(buf, sizeof(buf)); + + for (i = 0; archive_read_next_header(archive, &entry) == + ARCHIVE_OK; ++i) { + string archive_filename = archive_entry_pathname(entry); + string reject_dir = trim_filename(absroot + string("/") + string(PKG_REJECTED)); + string original_filename = trim_filename(absroot + string("/") + archive_filename); + string real_filename = original_filename; + + // Check if file is filtered out via INSTALL + if (non_install_list.find(archive_filename) != non_install_list.end()) { + mode_t mode; + + cout << utilname << ": ignoring " << archive_filename << endl; + + mode = archive_entry_mode(entry); + + if (S_ISREG(mode)) + archive_read_data_skip(archive); + + continue; + } + + // 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); + + archive_entry_set_pathname(entry, const_cast + (real_filename.c_str())); + + // Extract file + unsigned int flags = ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_UNLINK; + + if (archive_read_extract(archive, entry, flags) != ARCHIVE_OK) { + // 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 = archive_error_string(archive); + cerr << utilname << ": could not install " + archive_filename << ": " << msg << endl; + continue; + } + + // Check rejected file + if (real_filename != original_filename) { + bool remove_file = false; + mode_t mode = archive_entry_mode(entry); + + // Directory + if (S_ISDIR(mode)) + 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 (archive_errno(archive) == 0) + throw runtime_error("empty package"); + else + throw runtime_error("could not read " + filename); + } + + archive_read_finish(archive); +} + +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(), (char *) 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; + struct archive* archive; + struct archive_entry* entry; + + map hardlink_target_modes; + + // We first do a run over the archive and remember the modes + // of regular files. + // In the second run, we print the footprint - using the stored + // modes for hardlinks. + // + // FIXME the code duplication here is butt ugly + archive = archive_read_new(); + INIT_ARCHIVE(archive); + + if (archive_read_open_filename(archive, + const_cast(filename.c_str()), + ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) + throw runtime_error_with_errno("could not open " + filename, archive_errno(archive)); + + for (i = 0; archive_read_next_header(archive, &entry) == + ARCHIVE_OK; ++i) { + + mode_t mode = archive_entry_mode(entry); + + if (!archive_entry_hardlink(entry)) { + const char *s = archive_entry_pathname(entry); + + hardlink_target_modes[s] = mode; + } + + if (S_ISREG(mode) && archive_read_data_skip(archive)) + throw runtime_error_with_errno("could not read " + filename, archive_errno(archive)); + } + + archive_read_finish(archive); + + // Too bad, there doesn't seem to be a way to reuse our archive + // instance + archive = archive_read_new(); + INIT_ARCHIVE(archive); + + if (archive_read_open_filename(archive, + const_cast(filename.c_str()), + ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) + throw runtime_error_with_errno("could not open " + filename, archive_errno(archive)); + + for (i = 0; archive_read_next_header(archive, &entry) == + ARCHIVE_OK; ++i) { + mode_t mode = archive_entry_mode(entry); + + // Access permissions + if (S_ISLNK(mode)) { + // 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 { + const char *h = archive_entry_hardlink(entry); + + if (h) + cout << mtos(hardlink_target_modes[h]); + else + cout << mtos(mode); + } + + cout << '\t'; + + // User + uid_t uid = archive_entry_uid(entry); + struct passwd* pw = getpwuid(uid); + if (pw) + cout << pw->pw_name; + else + cout << uid; + + cout << '/'; + + // Group + gid_t gid = archive_entry_gid(entry); + struct group* gr = getgrgid(gid); + if (gr) + cout << gr->gr_name; + else + cout << gid; + + // Filename + cout << '\t' << archive_entry_pathname(entry); + + // Special cases + if (S_ISLNK(mode)) { + // Symlink + cout << " -> " << archive_entry_symlink(entry); + } else if (S_ISCHR(mode) || + S_ISBLK(mode)) { + // Device + cout << " (" << archive_entry_rdevmajor(entry) + << ", " << archive_entry_rdevminor(entry) + << ")"; + } else if (S_ISREG(mode) && + archive_entry_size(entry) == 0) { + // Empty regular file + cout << " (EMPTY)"; + } + + cout << '\n'; + + if (S_ISREG(mode) && archive_read_data_skip(archive)) + throw runtime_error_with_errno("could not read " + filename, archive_errno(archive)); + } + + if (i == 0) { + if (archive_errno(archive) == 0) + throw runtime_error("empty package"); + else + throw runtime_error("could not read " + filename); + } + + archive_read_finish(archive); +} + +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; +} + +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/src/pkgutil.h b/src/pkgutil.h new file mode 100644 index 0000000..30b41f8 --- /dev/null +++ b/src/pkgutil.h @@ -0,0 +1,110 @@ +// +// pkgutils +// +// Copyright (c) 2000-2005 Per Liden +// Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +// +// 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 set& non_install_files) 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)) {} + explicit runtime_error_with_errno(const string& msg, int e) throw() + : runtime_error(msg + string(": ") + strerror(e)) {} +}; + +// Utility functions +void assert_argument(char** argv, int argc, int index); +string itos(unsigned int value); +string mtos(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/src/rejmerge.8.in b/src/rejmerge.8.in new file mode 100644 index 0000000..baf8eb8 --- /dev/null +++ b/src/rejmerge.8.in @@ -0,0 +1,78 @@ +.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 Copyright (c) 2006-2007 CRUX team (http://crux.nu). +rejmerge (pkgutils) is licensed through the GNU General Public License. +Read the COPYING file for the complete license. diff --git a/src/rejmerge.conf b/src/rejmerge.conf new file mode 100644 index 0000000..c80af34 --- /dev/null +++ b/src/rejmerge.conf @@ -0,0 +1,5 @@ +# +# /etc/rejmerge.conf: rejmerge(8) configuration +# + +# End of file diff --git a/src/rejmerge.in b/src/rejmerge.in new file mode 100755 index 0000000..6570b77 --- /dev/null +++ b/src/rejmerge.in @@ -0,0 +1,316 @@ +#!/bin/bash +# +# rejmerge (pkgutils) +# +# Copyright (c) 2000-2005 Per Liden +# Copyright (c) 2006-2007 by CRUX team (http://crux.nu) +# +# 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