From fdf44680988ce53173262c8f6cb6b478a6ab04a9 Mon Sep 17 00:00:00 2001 From: Yann DIRSON Date: Tue, 11 Apr 2006 10:58:28 +0200 Subject: cvsps-2.1 tarball import --- CHANGELOG | 263 +++++ COPYING | 340 +++++++ Makefile | 35 + README | 230 +++++ cache.c | 528 ++++++++++ cache.h | 12 + cap.c | 135 +++ cap.h | 13 + cbtcommon/debug.c | 191 ++++ cbtcommon/debug.h | 75 ++ cbtcommon/hash.c | 272 +++++ cbtcommon/hash.h | 52 + cbtcommon/inline.h | 23 + cbtcommon/list.h | 112 +++ cbtcommon/rcsid.h | 21 + cbtcommon/sio.c | 87 ++ cbtcommon/sio.h | 29 + cbtcommon/tcpsocket.c | 257 +++++ cbtcommon/tcpsocket.h | 33 + cbtcommon/text_util.c | 317 ++++++ cbtcommon/text_util.h | 43 + cvs_direct.c | 925 +++++++++++++++++ cvs_direct.h | 24 + cvsps.1 | 205 ++++ cvsps.c | 2619 +++++++++++++++++++++++++++++++++++++++++++++++++ cvsps.h | 34 + cvsps.spec | 48 + cvsps_types.h | 162 +++ list_sort.c | 82 ++ list_sort.h | 13 + merge_utils.sh | 50 + stats.c | 141 +++ stats.h | 11 + util.c | 291 ++++++ util.h | 27 + 35 files changed, 7700 insertions(+) create mode 100644 CHANGELOG create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README create mode 100644 cache.c create mode 100644 cache.h create mode 100644 cap.c create mode 100644 cap.h create mode 100644 cbtcommon/debug.c create mode 100644 cbtcommon/debug.h create mode 100644 cbtcommon/hash.c create mode 100644 cbtcommon/hash.h create mode 100644 cbtcommon/inline.h create mode 100644 cbtcommon/list.h create mode 100644 cbtcommon/rcsid.h create mode 100644 cbtcommon/sio.c create mode 100644 cbtcommon/sio.h create mode 100644 cbtcommon/tcpsocket.c create mode 100644 cbtcommon/tcpsocket.h create mode 100644 cbtcommon/text_util.c create mode 100644 cbtcommon/text_util.h create mode 100644 cvs_direct.c create mode 100644 cvs_direct.h create mode 100644 cvsps.1 create mode 100644 cvsps.c create mode 100644 cvsps.h create mode 100644 cvsps.spec create mode 100644 cvsps_types.h create mode 100644 list_sort.c create mode 100644 list_sort.h create mode 100644 merge_utils.sh create mode 100644 stats.c create mode 100644 stats.h create mode 100644 util.c create mode 100644 util.h diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..f86b1d8 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,263 @@ +2.1 +- lots of fixes to get the thing working sufficienly for cvs2git +- main features + - optionally detect and display ancestor branch information for + new detected branches + - never allow more than one revision of a file to be in a single + patchset (this may disturb the patchset numbers for historical + data :-( + - use file revisions to help sort, so that initial imports + won't be listed as patchset 1, with patchset 2 as the actual + creation of the files. + - reports log times in localtime (use TZ to override) +- many downstream patches from Debian, special thanks to Marcus + Crafter for maintaining. From the Debian changelog: + - patch from Kim Hansen to fix time calculation bug + - documentation patch from J. Bruce Fields + - directory name handling patch from David Kilzer + - others I'm not sure of, I'd happily update the credit here + +2.0rc1 +- resolve -r symbols during global symbol resolution +- using two -r arguments implicitly sets -b with branch + from second -r (if you can speak cvsps arguments, you'll + understand this) +- fix using cvsps against sourceforge where the actual + server repository path has many instances of the project + name in it and the 'strstr' was finding the wrong one. + instead use the equiv. of 'strrstr' + +2.0b10 +- fix bug where filenames with 'bad' characters (such as ' ') + were being sent unescaped to a shell +- add --no-cvs-direct and -Z 0 to disable cvs-direct and compression +- extend the 'fuzz-factor' concept by keeping a min/max date + encountered for each patchset and fuzz on this interval, + rather than fuzzing on the date of the first encountered + member only, helps for handling REALLY slow commits where a + HUGE number of individual files were modified at a slow trickle. +- it turns out that old versions of cvs have two problems + 1) they don't support 'rlog' (handled) + 2) they don't support 'version', which caused the workaround + for 1) to fail. + Now cvsps detects cvs versions too old to support version and/or + rlog. Additionally, if server is too old, cvs-direct is + simply disabled. +- parse the rc file first so that the command line will override +- add -q to shut-up the warnings + +2.0b9 +- remove Mbinary from valid-response list - can't handle it +- much more (pedantic?) clean-up/close path in cvs_direct + to fix all of the lost data and hanging problems at close +- added rlog capability to cvs_direct, switched main code + to use it. +- added version capability to cvs_direct, now cvsps is + completely independant of cvs binary for all operations! +- added --root and repository command-line arguments, + now cvsps is completely indepentant of having working + directory! + +2.0b8 +- fix the cvs_rupdate calls to use the real repository + instead of the mangled use_rep_path. This broke + cvs_direct generation of add/remove diffs +- fix the sed expression to account for the difference + between +++ and --- in the first and second lines. + This broke remove diffs for direct and external. +- attempt to fix a hang-at-flush bug in cvs-direct + compression handling + +2.0b7 +- implement compression in cvs_direct +- add command line -Z to specify compression level. + applies to cvs-direct and external cvs +- implement 'cvs_diff' in cvs-direct +- use cvs_diff instead of cvs_rdiff because it allows + us to specify all of the lovely diff options +- change system to my_system so ctrl-c will get + through. indirectly, this is necessary because + cvs has loused up the exit codes with the 'cvs diff' + command, making it impossible to determine whether + the program exited abnormally +- add parsing of ~/.cvsps/cvspsrc file at startup + where command line arguments can be listed, one + per line (parameters to arguments on the same line + as the argument). --norc prevents this + +2.0b6 +- fix bug where if -r was specified along with other + filter options, the other filter options were + applied first, and the start/end tags were + never found. +- change -f from strstr to regex +- change regex to EXTENDED regex +- add the --cvs-direct option + +2.0b5 +- add a cvs_check_cap for capability checking. +- use cvs_check_cap to check whether rlog is + supported. it's not supported in 1.11 and + before. warn if non-supported version is + detected +- add --no-rcmds to manually disable rlog and rdiff +- add a fix for some bizarre inconsistencies found + in some random repositories, such as tagged + revisions that don't exist. +- remove the adaptive crap filter and bk_log_border + logic now that Larry has cleaned up the exported + BK->CVS trees. + +2.0b4 +- add the bk_log_border parsing logic to handle the + case where 'cvs log' text was committed into the log +- add 'adaptive crap filter' logic to handle all + of the different corruptions of the cvs log file +- switch to rdiff instead of diff to not require working + files to be checked out. Note: when --diff-opts are + specified, diff must be used (not rdiff) because + rdiff doesn't support options(?!!) + +2.0b3 +- add the --diff-opts for explicitly setting the diff + options. important for setting increased context for + example. +- use 'cvs rlog' instead of 'cvs log'. important if + there are empty directory pruned with cvs update -P + for getting consistent patchset numbering +- clean up patch_set_affects_branch for handling INVALID + tags and the -r especially around a branch point +- add the bkcvs proof-of-concept hack + + +2.0b2 +- change the way INVALID tags are reported - show the exact + patchsets and revisions that conflict, and don't print + anything for 'funky' tags/revisions +- add psid to the patchset structure and pass through tree + once just to assign the numbers. this allows reporting + psid when problems are found (above) +- more code restructuring - move stats to own module +- commenting cleanups +- include the very rudimentary merge_utils.sh functions + I use to assist merging stuff +- change the way diffs are generated, handle individual + members that are before/after the -r tag restrictions +- change the semantics of the -s argument. it now only + restricts the patch sets the same as the other filtration + arguments. new argument -g turns on diff generation +- add manpage section discussing tag handling semantics + +2.0b1 +- major restructuring of source code. single cvsps.c split into + a few modules +- major rework of the data structures in order to support the most + frequently requested, and most desired feature: -r. cvsps + can now associated symbolic tags with specific patchsets, + branches are now associated with the patchset instead of + the individual revision. you can view patchsets committed + after a given symbolic tag, or between two tags. +- changed the handling of same author, same log message commits + on different branches. this now creates multiple patchsets +- changed the handling of 'file xyz initially added on branch xyz' + log messages, they now create hidden patchsets. +- these last two items have made the patchset numbering scheme + incompatible with the old scheme. patchsets are going + to change numbers. + +1.99.1 (not released) +- redid the data structures a bit to 'normalize' the references to + a file revision. this allows the program to detect the case + when a file is added on a branch, and generate the right output + for 'cvsps -s' in this case (frequent bug report) +- added the '-l' option to restrict patchsets based on log descr + content. (patch from Geoff Soutter). added man page for it. +- added the --summary-first option suggested by Andi Kleen to + have the PatchSet summary information for all patchsets at + the head of the output, when multiple patchsets are given + to the -s option. +- added the all: target to Makefile. (patch from Ben Elliston) +- use a single centralized cvsps.cache file per root/repository + this obviates the need to 'cvsps -u' in multiple checked out + trees of the same repository (patch adopted from Baruch Even) +- change the magic name TRUNK (for -b option) to HEAD to be + consistent with cvs. (patch from Henrik Nordstrom) +- when parsing 'cvs log' output, look for the exact strings + that separate the log entries and the files, instead of + just the first 8 characters. this allows a wider variety + of stuff to be in the log format (including actual patchsets). + Inspired by Andy Isaacson. +- added the -p option allowing patchset output to go into + individual files in a named directory. Based on a patch + by Henrik Nordstrom. This is a great idea. Thanks Henrik. + +1.3.3 (why isn't this 1.4 again?) +- added the 'memory reduction' patch from Baruch Even. This patch + greatly reduces the memory footprint of CVSps against a large + repository by using dynamically allocated buffers, and by using + a tree to hold "common" strings (to avoid having a thousand copies + of the string "1.1"). This patch also adds a new option '-t' which + displays some memory usage statistics at the top. + +- added the 00-strip-revision.patch from Steven Tweedie which fixes + a parsing problem when there are locked files in the repository + +- added the 'Tweedie Tweenie' patch from Steven Tweedie (01-stable-tree) + which fixes a bug where interspersed commits could cause unstable + tree behavior. This would happen especially when multiple users commit + at the same time to different parts of a large tree over a slow link. + +- added the --norc option (based on a suggestion by Soren S. Jorvang) + to handle cases where people have bad stuff in their .cvsrc which makes + cvsps fail. I could have added the '-f' unconditionally to the command + line for cvs when run under cvsps, but somehow that seemed dangerous. + +1.3.2 (small feature release) +- added the 'multi-patchset' feature to the -s option, provided in its + entirety by Daiki Ueno + +1.3.1 (bugfix release mostly) +- fix bug with updating cache (-u) having to do with matching new and old + revisions + +- fix timestamp_fuzz_factor bug where the fuzz was applied to loading + from cache by mistake. + +- add a spec file (from Jan IVEN ) + +- improve the parameterization of Makefile, and make things relocatable + (from many people, esp. Amitai Schlair ) + +- fix strip_path_len calculation (again - I had munged the prior fix) + Jeffrey Ebert + + +1.3 +- fix case where CVSROOT is initial substring of Repository when + creating strip_path variable. + + Thanks to Jean-Michel Rouet for + this fix. + +- fix case where a subdirectory has a different CVSROOT or Repository + path from the toplevel directory (happens when one project is checked + out inside another projects subdirectory). + + Thanks to Philippe M. Chiasson for the fix. + + +1.2 + +- Moved cache file location to CVS/ subdirectory. This is where CVS keeps + it's meta-data, and putting the cache there keeps it out of the way of + other CVS operations. (thanks to Amitai Schlair for suggestion) + +- Added manual page. (thanks to Amitai Schlair for suggestion) + +- Fixed the '-b' option to take a special branch name 'TRUNK' which + will restrict the output to patchsets made on the main trunk. + + +1.1 Initial public release. +-------- end of changelog --------- + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + 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) + + 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 + + +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) year 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/Makefile b/Makefile new file mode 100644 index 0000000..507c3e9 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +MAJOR=2 +MINOR=1 +CC?=gcc +CFLAGS?=-g -O2 -Wall +CFLAGS+=-I. -DVERSION=\"$(MAJOR).$(MINOR)\" +prefix?=/usr/local +OBJS=\ + cbtcommon/debug.o\ + cbtcommon/hash.o\ + cbtcommon/text_util.o\ + cbtcommon/sio.o\ + cbtcommon/tcpsocket.o\ + cvsps.o\ + cache.o\ + util.o\ + stats.o\ + cap.o\ + cvs_direct.o\ + list_sort.o + +all: cvsps + +cvsps: $(OBJS) + $(CC) -o cvsps $(OBJS) -lz + +install: + [ -d $(prefix)/bin ] || mkdir -p $(prefix)/bin + [ -d $(prefix)/share/man/man1 ] || mkdir -p $(prefix)/share/man/man1 + install cvsps $(prefix)/bin + install -m 644 cvsps.1 $(prefix)/share/man/man1 + +clean: + rm -f cvsps *.o cbtcommon/*.o core + +.PHONY: install clean diff --git a/README b/README new file mode 100644 index 0000000..a8724fa --- /dev/null +++ b/README @@ -0,0 +1,230 @@ +CVSps (c) 2001,2002,2003 David Mansfield +------------------------------ + +Overview. +-------- + +CVSps is a program for generating 'patchset' information from a CVS +repository. A patchset in this case is defined as a set of changes made +to a collection of files, and all committed at the same time (using a +single 'cvs commit' command). This information is valuable to seeing the +big picture of the evolution of a cvs project. While cvs tracks revision +information, it is often difficult to see what changes were committed +'atomically' to the repository. + +Compiling. +--------- + +CVSps is a relatively small program, with only a few modules. +The Makefile is very simple and should work in most GNU type environments. +Unfortunately, I've only been able to test on Red Hat Linux, so YMMV. As +CVSps matures, I'm sure a more sophisticated build environment will evolve +with it. For now, just try 'make' and 'make install'. If you have any +problems let me know. + +Running. +------- + +Note: not all options are necessarily discussed here. Please check the +output of 'cvsps -h' and/or the manual page for the most up-to-date info. + +CVSps operates by parsing the 'cvs log' output. So to run it, you must +be in the working directory of a cvs project. CVSps handles +subdirectories fine, so run it in the top directory of your project. + +a) the ~/.cvsps/cvsps.cache file (or so it's called) + +Because you may have a *lot* of revision history in your project, and/or +your connection to the cvs server may be slow or congested, CVSps uses a +cache file for most operations. The first time you run CVSps, just issue: + +cvsps + +and will begin reading and parsing the cvs log. When it is finished it +will output all of the patchset information to stdout, and it will also +generate the '~/.cvsps/cvsps.cache' file. Note: for historical reasons +this file is still called the cvsps.cache file, but in reality it is named +based on the CVS/Root and CVS/Repository contents, and thus is shared for +the same repository checked out in multiple places. + +If the cache file exists, it won't ever be automatically updated. To +update the cache with cvs activity that has occurred since the ~/.cvsps/cvsps.cache +was last updated, use: + +cvsps -u + +If you question the integrity of the ~/.cvsps/cvsps.cache, or for some other reason +want to force a full cache rebuild, use (you could also 'rm' the cache file): + +cvsps -x + +b) Reading the output. + +CVSps's output is information about patchsets. A patchset looks like: + +--------------------- +PatchSet 999 +Date: 2002/07/11 19:50:46 +Author: alan +Branch: HEAD +Tag: (none) +Log: +[PATCH] Fix several pdc202xx problems + +Misnaming of 20270 as 20268R +Failure of LBA48 on 20262 +Incorrect speed detection because the old driver used host not drive side +cable detect +PDC202xx handling for quirks in udma reporting off some drives +LBA48 for PIO mode + +BKrev: 3d2dd386wJMnehoOAhv3wL991IfXVQ + +Members: + ChangeSet:1.999->1.1000 + MAINTAINERS:1.74->1.75 + drivers/ide/ide-features.c:1.4->1.5 + drivers/ide/ide-pci.c:1.18->1.19 + drivers/ide/pdc202xx.c:1.11->1.12 + include/linux/pci_ids.h:1.44->1.45 + +--------------------- + +This patchset is taken from the linux kernel BK->CVS tree. It shows the date, +the author, log message and each file that was modified. For each file the +pre-commit and post-commit revisions are given. You can also see (if +applicable, not in this case) if the files are on a branch, as well as the +tag (see TAGS below). + +Patchsets are ordered by commit timestamp, so as long as the clock on your +cvs server is monotonic, the numbering of patchsets should be invariant +across cache-rebuilds. (see COMPATIBILITY below). + +c) Limiting the patchset output. + +The default output of CVSps is to show all patchsets. This can be +filtered in one of many ways. These flags can be combined to really +limit the output to what you're interested in. + +By id. With the -s you can specify individual PatchSets by +number or by range. Ranges can be of the form '', '-', +'-' and of course '-'. Multiple ranges can be +specified seperated by commas. E.g. + +cvsps -s 999-1020,1025,4956- + +By author. With the -a flag you limit the output to patchsets +committed by a given author. The author is usually the UNIX login id. + +By file. With the -f flag you limit the output to patchsets +that have modified the given file. Because a regular expression can have +many pieces 'or'ed together, you can specify many different files here, +for example (note also the use of the ^ character): + +cvsps -f '^net/ipv4|^net/core' + +By date. With one date specification, CVSps shows only patchsets newer +than the date given, and with two dates, it shows patchsets between the +two dates. + +*NOTE ON DATE FORMAT*. Because I'm lazy, only one date format is +currently acceptable. 'YYYY/MM/DD HH:MM:SS' where time is given as +localtime, and HH is in 24 hour format. NOTE ALSO that cvs tends to +display times as GMT, but parse dates as localtime, so when using the '-D' +with cvs you need to convert from GMT to localtime in your head. CVSps is +not as fancy. It treats all dates as if in localtime, and therefore you +give CVSps dates the same way it gives them to you. + +By branch. With the -b flag you limit the output to patchsets +that have modified the history of the given branch. Note, this +doesn't necessarily mean the commit itself was made on the branch, since +the files in question may have existed prior to the branch point, in which +case changes made to a given file before the branch point affect the file +as it exists in the head of the branch. If you want to restrict to the +main branch, use a branch of 'HEAD'. + +By log comment. With the -l flag you can limit the ouptut to +patchsets with the commit message matching the regex. + +By tag. With the -r -r you can limit the patchsets to +commits after a given tag1 and, optionally, before tag2. + +d) viewing the changes made by a patchset. + +To show the 'diff' output for a given patchset, use -g. + +It will show you the diff of changes made by the selected commits. +Some effort was made to ensure that the patches are valid, even in the +case of removing or creating files, a case in which 'cvs diff' fails. +The patches generated are, generally speaking, applyable in the working +directory with the '-p1' option to the patch command. + +e) what is timestamp fuzz factor (-z option)? + +There's another annoying feature of cvs. When you commit a large change, +the timestamp on the change is created per file committed. For example: +if you commit changes to 60 files on a slow server, taking, say, 60 +seconds, the 'commit time' as given in the log message for the first file +will differ from that of the last file by 60 seconds. + +The fuzz factor attempts to workaround this by saying: commits by the same +author, using the same log message, within seconds are considered +part of the same patchset. The default fuzz is 300 seconds (5 minutes). + +TAGS +---- + +Please read the manual page. + +COMPATIBILITY +------------- + +One of the main goals of cvsps was to make the patchset numbering stable across +all time, as long as no funny-business is done to the repository files themselves. + +Unfortunately, as bugs have been fixed, the numbering has changed. This is most +regrettable, but unavoidable. + +Additionally, in version 2.0, two changes have been made which will 'renumber' +the patch sets. + +1) The false 'globbing' of two commits from nearly the exact same time, by the +same person, with the same log description but to different branches. Now, +these will be reported as 2 patchsets instead of one. + +2) The creation of a large volume of patchsets for 'file xyz was originally added on +branch' log messages. This occurs whenever a file is originally born on a branch, +and is exacerbated by the fact that even when all of these files are created with +a single commit, the 'file xyz...' messages, which contains the actual file name, +are different, causing a proliferation of these unwanted patchsets. These patchsets +are now silently eliminated from the output. + +Reporting bugs / submitting patches. +----------------------------------- + +Although the current version is perfect and bug free, you can still send +bug reports, feature requests and patches to me at: + +cvsps@dm.cobite.com + +I will try to maintain CVSps and make releases regularly. The most recent +version of CVSps will always be available at http://www.cobite.com/cvsps + +Special thanks to my employer Cobite and Robert Lippman, who've given me +time to develop this tool. + +Known Problems (this will become the FAQ if anyone ever A any Q). +---------------------------------------------------------------- + +1) What is the '*** file xyz doesn't match strip_path abc' error? + +This error occurs when one of the subdirectories of the directory you +ran CVSps in is checked out from a different repository. CVSps tries +to remove the repository path information from the filenames that it +gets to give you working-directory local pathnames. It does this +at startup by parsing the CVS/Root and CVS/Repository files. If +the contents of these two files is different for some subdirectory, +all of the files in that subdirectory will be ignored. + +You can always run CVSps in that subdirectory, and since it IS a +separate repository, that does make a little bit of sense. diff --git a/cache.c b/cache.c new file mode 100644 index 0000000..4c51cf7 --- /dev/null +++ b/cache.c @@ -0,0 +1,528 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "cache.h" +#include "cvsps_types.h" +#include "cvsps.h" +#include "util.h" + +#define CACHE_DESCR_BOUNDARY "-=-END CVSPS DESCR-=-\n" + +/* change this when making the on-disk cache-format invalid */ +static int cache_version = 1; + +/* the tree walk API pretty much requries use of globals :-( */ +static FILE * cache_fp; +static int ps_counter; + +static void write_patch_set_to_cache(PatchSet *); +static void parse_cache_revision(PatchSetMember *, const char *); +static void dump_patch_set(FILE *, PatchSet *); + +static FILE *cache_open(char const *mode) +{ + char *prefix; + char fname[PATH_MAX]; + char root[PATH_MAX]; + char repository[PATH_MAX]; + FILE * fp; + + /* Get the prefix */ + prefix = get_cvsps_dir(); + if (!prefix) + return NULL; + + /* Generate the full path */ + strcpy(root, root_path); + strcpy(repository, repository_path); + + strrep(root, '/', '#'); + strrep(repository, '/', '#'); + + snprintf(fname, PATH_MAX, "%s/%s#%s", prefix, root, repository); + + if (!(fp = fopen(fname, mode)) && *mode == 'r') + { + if ((fp = fopen("CVS/cvsps.cache", mode))) + { + fprintf(stderr, "\n"); + fprintf(stderr, "****WARNING**** Obsolete CVS/cvsps.cache file found.\n"); + fprintf(stderr, " New file will be re-written in ~/%s/\n", CVSPS_PREFIX); + fprintf(stderr, " Old file will be ignored.\n"); + fprintf(stderr, " Please manually remove the old file.\n"); + fprintf(stderr, " Continuing in 5 seconds.\n"); + sleep(5); + fclose(fp); + fp = NULL; + } + } + + return fp; +} + +/* ************ Reading ************ */ + +enum +{ + CACHE_NEED_FILE, + CACHE_NEED_BRANCHES, + CACHE_NEED_SYMBOLS, + CACHE_NEED_REV, + CACHE_NEED_PS, + CACHE_NEED_PS_DATE, + CACHE_NEED_PS_AUTHOR, + CACHE_NEED_PS_TAG, + CACHE_NEED_PS_TAG_FLAGS, + CACHE_NEED_PS_BRANCH, + CACHE_NEED_PS_BRANCH_ADD, + CACHE_NEED_PS_DESCR, + CACHE_NEED_PS_EOD, + CACHE_NEED_PS_MEMBERS, + CACHE_NEED_PS_EOM +}; + +time_t read_cache() +{ + FILE * fp; + char buff[BUFSIZ]; + int state = CACHE_NEED_FILE; + CvsFile * f = NULL; + PatchSet * ps = NULL; + char datebuff[20] = ""; + char authbuff[AUTH_STR_MAX] = ""; + char tagbuff[LOG_STR_MAX] = ""; + int tag_flags = 0; + char branchbuff[LOG_STR_MAX] = ""; + int branch_add = 0; + char logbuff[LOG_STR_MAX] = ""; + time_t cache_date = -1; + int read_version; + + if (!(fp = cache_open("r"))) + goto out; + + /* first line is cache version format "cache version: %d\n" */ + if (!fgets(buff, BUFSIZ, fp) || strncmp(buff, "cache version:", 14)) + { + debug(DEBUG_APPERROR, "bad cvsps.cache file"); + goto out_close; + } + + if ((read_version = atoi(buff + 15)) != cache_version) + { + debug(DEBUG_APPERROR, "bad cvsps.cache version %d, expecting %d. ignoring cache", + read_version, cache_version); + goto out_close; + } + + /* second line is date cache was created, format "cache date: %d\n" */ + if (!fgets(buff, BUFSIZ, fp) || strncmp(buff, "cache date:", 11)) + { + debug(DEBUG_APPERROR, "bad cvsps.cache file"); + goto out_close; + } + + cache_date = atoi(buff + 12); + debug(DEBUG_STATUS, "read cache_date %d", (int)cache_date); + + while (fgets(buff, BUFSIZ, fp)) + { + int len = strlen(buff); + + switch(state) + { + case CACHE_NEED_FILE: + if (strncmp(buff, "file:", 5) == 0) + { + len -= 6; + f = create_cvsfile(); + f->filename = xstrdup(buff + 6); + f->filename[len-1] = 0; /* Remove the \n at the end of line */ + debug(DEBUG_STATUS, "read cache filename '%s'", f->filename); + put_hash_object_ex(file_hash, f->filename, f, HT_NO_KEYCOPY, NULL, NULL); + state = CACHE_NEED_BRANCHES; + } + else + { + state = CACHE_NEED_PS; + } + break; + case CACHE_NEED_BRANCHES: + if (buff[0] != '\n') + { + char * tag; + + tag = strchr(buff, ':'); + if (tag) + { + *tag = 0; + tag += 2; + buff[len - 1] = 0; + cvs_file_add_branch(f, buff, tag); + } + } + else + { + f->have_branches = 1; + state = CACHE_NEED_SYMBOLS; + } + break; + case CACHE_NEED_SYMBOLS: + if (buff[0] != '\n') + { + char * rev; + + rev = strchr(buff, ':'); + if (rev) + { + *rev = 0; + rev += 2; + buff[len - 1] = 0; + cvs_file_add_symbol(f, rev, buff); + } + } + else + { + state = CACHE_NEED_REV; + } + break; + case CACHE_NEED_REV: + if (isdigit(buff[0])) + { + char * p = strchr(buff, ' '); + if (p) + { + CvsFileRevision * rev; + *p++ = 0; + buff[len-1] = 0; + rev = cvs_file_add_revision(f, buff); + if (strcmp(rev->branch, p) != 0) + { + debug(DEBUG_APPERROR, "branch mismatch for %s:%s %s != %s", + rev->file->filename, rev->rev, rev->branch, p); + } + } + } + else + { + state = CACHE_NEED_FILE; + } + break; + case CACHE_NEED_PS: + if (strncmp(buff, "patchset:", 9) == 0) + state = CACHE_NEED_PS_DATE; + break; + case CACHE_NEED_PS_DATE: + if (strncmp(buff, "date:", 5) == 0) + { + /* remove prefix "date: " and LF from len */ + len -= 6; + strzncpy(datebuff, buff + 6, MIN(len, sizeof(datebuff))); + state = CACHE_NEED_PS_AUTHOR; + } + break; + case CACHE_NEED_PS_AUTHOR: + if (strncmp(buff, "author:", 7) == 0) + { + /* remove prefix "author: " and LF from len */ + len -= 8; + strzncpy(authbuff, buff + 8, MIN(len, AUTH_STR_MAX)); + state = CACHE_NEED_PS_TAG; + } + break; + case CACHE_NEED_PS_TAG: + if (strncmp(buff, "tag:", 4) == 0) + { + /* remove prefix "tag: " and LF from len */ + len -= 5; + strzncpy(tagbuff, buff + 5, MIN(len, LOG_STR_MAX)); + state = CACHE_NEED_PS_TAG_FLAGS; + } + break; + case CACHE_NEED_PS_TAG_FLAGS: + if (strncmp(buff, "tag_flags:", 10) == 0) + { + /* remove prefix "tag_flags: " and LF from len */ + len -= 11; + tag_flags = atoi(buff + 11); + state = CACHE_NEED_PS_BRANCH; + } + break; + case CACHE_NEED_PS_BRANCH: + if (strncmp(buff, "branch:", 7) == 0) + { + /* remove prefix "branch: " and LF from len */ + len -= 8; + strzncpy(branchbuff, buff + 8, MIN(len, LOG_STR_MAX)); + state = CACHE_NEED_PS_BRANCH_ADD; + } + break; + case CACHE_NEED_PS_BRANCH_ADD: + if (strncmp(buff, "branch_add:", 11) == 0) + { + /* remove prefix "branch_add: " and LF from len */ + len -= 12; + branch_add = atoi(buff + 12); + state = CACHE_NEED_PS_DESCR; + } + break; + case CACHE_NEED_PS_DESCR: + if (strncmp(buff, "descr:", 6) == 0) + state = CACHE_NEED_PS_EOD; + break; + case CACHE_NEED_PS_EOD: + if (strcmp(buff, CACHE_DESCR_BOUNDARY) == 0) + { + debug(DEBUG_STATUS, "patch set %s %s %s %s", datebuff, authbuff, logbuff, branchbuff); + ps = get_patch_set(datebuff, logbuff, authbuff, branchbuff, NULL); + /* the tag and tag_flags will be assigned by the resolve_global_symbols code + * ps->tag = (strlen(tagbuff)) ? get_string(tagbuff) : NULL; + * ps->tag_flags = tag_flags; + */ + ps->branch_add = branch_add; + state = CACHE_NEED_PS_MEMBERS; + } + else + { + /* Make sure we have enough in the buffer */ + if (strlen(logbuff)+strlen(buff)file = (CvsFile*)get_hash_object(file_hash, filename); + + if (!psm->file) + { + debug(DEBUG_APPERROR, "file '%s' not found in hash", filename); + exit(1); + } + + psm->pre_rev = file_get_revision(psm->file, pre); + psm->post_rev = file_get_revision(psm->file, post); + psm->post_rev->dead = dead; + psm->post_rev->post_psm = psm; + + if (!bp) + { + if (psm->pre_rev) + psm->pre_rev->pre_psm = psm; + } + else + { + list_add(&psm->post_rev->link, &psm->pre_rev->branch_children); + } +} + +/************ Writing ************/ + +void write_cache(time_t cache_date) +{ + struct hash_entry * file_iter; + + ps_counter = 0; + + if ((cache_fp = cache_open("w")) == NULL) + { + debug(DEBUG_SYSERROR, "can't open cvsps.cache for write"); + return; + } + + fprintf(cache_fp, "cache version: %d\n", cache_version); + fprintf(cache_fp, "cache date: %d\n", (int)cache_date); + + reset_hash_iterator(file_hash); + + while ((file_iter = next_hash_entry(file_hash))) + { + CvsFile * file = (CvsFile*)file_iter->he_obj; + struct hash_entry * rev_iter; + + fprintf(cache_fp, "file: %s\n", file->filename); + + reset_hash_iterator(file->branches); + while ((rev_iter = next_hash_entry(file->branches))) + { + char * rev = (char *)rev_iter->he_key; + char * tag = (char *)rev_iter->he_obj; + fprintf(cache_fp, "%s: %s\n", rev, tag); + } + + fprintf(cache_fp, "\n"); + + reset_hash_iterator(file->symbols); + while ((rev_iter = next_hash_entry(file->symbols))) + { + char * tag = (char *)rev_iter->he_key; + CvsFileRevision * rev = (CvsFileRevision*)rev_iter->he_obj; + + if (rev->present) + fprintf(cache_fp, "%s: %s\n", tag, rev->rev); + } + + fprintf(cache_fp, "\n"); + + reset_hash_iterator(file->revisions); + while ((rev_iter = next_hash_entry(file->revisions))) + { + CvsFileRevision * rev = (CvsFileRevision*)rev_iter->he_obj; + if (rev->present) + fprintf(cache_fp, "%s %s\n", rev->rev, rev->branch); + } + + fprintf(cache_fp, "\n"); + } + + fprintf(cache_fp, "\n"); + walk_all_patch_sets(write_patch_set_to_cache); + fclose(cache_fp); + cache_fp = NULL; +} + +static void write_patch_set_to_cache(PatchSet * ps) +{ + dump_patch_set(cache_fp, ps); +} + +static void dump_patch_set(FILE * fp, PatchSet * ps) +{ + struct list_head * next = ps->members.next; + + ps_counter++; + fprintf(fp, "patchset: %d\n", ps_counter); + fprintf(fp, "date: %d\n", (int)ps->date); + fprintf(fp, "author: %s\n", ps->author); + fprintf(fp, "tag: %s\n", ps->tag ? ps->tag : ""); + fprintf(fp, "tag_flags: %d\n", ps->tag_flags); + fprintf(fp, "branch: %s\n", ps->branch); + fprintf(fp, "branch_add: %d\n", ps->branch_add); + fprintf(fp, "descr:\n%s", ps->descr); /* descr is guaranteed to end with LF */ + fprintf(fp, CACHE_DESCR_BOUNDARY); + fprintf(fp, "members:\n"); + + while (next != &ps->members) + { + PatchSetMember * psm = list_entry(next, PatchSetMember, link); + int bp = 1; + + /* this actually deduces if this revision is a branch point... */ + if (!psm->pre_rev || (psm->pre_rev->pre_psm && psm->pre_rev->pre_psm == psm)) + bp = 0; + + fflush(fp); + + fprintf(fp, "file:%s; pre_rev:%s; post_rev:%s; dead:%d; branch_point:%d\n", + psm->file->filename, + psm->pre_rev ? psm->pre_rev->rev : "INITIAL", psm->post_rev->rev, + psm->post_rev->dead, bp); + next = next->next; + } + + fprintf(fp, "\n"); +} + +/* where's arithmetic?... */ diff --git a/cache.h b/cache.h new file mode 100644 index 0000000..996c4bc --- /dev/null +++ b/cache.h @@ -0,0 +1,12 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifndef CACHE_H +#define CACHE_H + +extern time_t read_cache(); +extern void write_cache(time_t); + +#endif /* CACHE_H */ diff --git a/cap.c b/cap.c new file mode 100644 index 0000000..a6186f6 --- /dev/null +++ b/cap.c @@ -0,0 +1,135 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#include +#include +#include + +#include +#include + +#include "cap.h" +#include "cvs_direct.h" + +extern CvsServerCtx * cvs_direct_ctx; + +static char client_version[BUFSIZ]; +static char server_version[BUFSIZ]; + +static int check_cvs_version(int, int, int); +static int check_version_string(const char *, int, int, int); + +int cvs_check_cap(int cap) +{ + int ret; + + switch(cap) + { + case CAP_HAVE_RLOG: + if (!(ret = check_cvs_version(1,11,1))) + { + debug(DEBUG_APPERROR, + "WARNING: Your CVS client version:\n[%s]\n" + "and/or server version:\n[%s]\n" + "are too old to properly support the rlog command. \n" + "This command was introduced in 1.11.1. Cvsps\n" + "will use log instead, but PatchSet numbering\n" + "may become unstable due to pruned empty\n" + "directories.\n", client_version, server_version); + } + break; + + default: + debug(DEBUG_APPERROR, "unknown cvs capability check %d", cap); + exit(1); + } + + return ret; +} + +static void get_version_external() +{ + FILE * cvsfp; + + strcpy(client_version, "(UNKNOWN CLIENT)"); + strcpy(server_version, "(UNKNOWN SERVER)"); + + if (!(cvsfp = popen("cvs version 2>/dev/null", "r"))) + { + debug(DEBUG_APPERROR, "cannot popen cvs version. exiting"); + exit(1); + } + + if (!fgets(client_version, BUFSIZ, cvsfp)) + { + debug(DEBUG_APPMSG1, "WARNING: malformed CVS version: no data"); + goto out; + } + + chop(client_version); + + if (strncmp(client_version, "Client", 6) == 0) + { + if (!fgets(server_version, BUFSIZ, cvsfp)) + { + debug(DEBUG_APPMSG1, "WARNING: malformed CVS version: no server data"); + goto out; + } + chop(server_version); + } + else + { + server_version[0] = 0; + } + + out: + pclose(cvsfp); +} + +int check_cvs_version(int req_major, int req_minor, int req_extra) +{ + if (!client_version[0]) + { + if (cvs_direct_ctx) + cvs_version(cvs_direct_ctx, client_version, server_version); + else + get_version_external(); + } + + return (check_version_string(client_version, req_major, req_minor, req_extra) && + (!server_version[0] || check_version_string(server_version, req_major, req_minor, req_extra))); +} + +int check_version_string(const char * str, int req_major, int req_minor, int req_extra) +{ + char * p; + int major, minor, extra; + int skip = 6; + + p = strstr(str, "(CVS) "); + + if (!p) { + p = strstr(str, "(CVSNT)"); + skip = 8; + } + + if (!p) + { + debug(DEBUG_APPMSG1, "WARNING: malformed CVS version str: %s", str); + return 0; + } + + p += skip; + if (sscanf(p, "%d.%d.%d", &major, &minor, &extra) != 3) + { + debug(DEBUG_APPMSG1, "WARNING: malformed CVS version: %s", str); + return 0; + } + + return (major > req_major || + (major == req_major && minor > req_minor) || + (major == req_major && minor == req_minor && extra >= req_extra)); +} + diff --git a/cap.h b/cap.h new file mode 100644 index 0000000..7634015 --- /dev/null +++ b/cap.h @@ -0,0 +1,13 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifndef CAP_H +#define CAP_H + +#define CAP_HAVE_RLOG 1 + +int cvs_check_cap(int); + +#endif /* CAP_H */ diff --git a/cbtcommon/debug.c b/cbtcommon/debug.c new file mode 100644 index 0000000..3f5ac52 --- /dev/null +++ b/cbtcommon/debug.c @@ -0,0 +1,191 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#include +#include +#include +#include +#include + +#include "debug.h" +#include "rcsid.h" + +#ifdef _WIN32 +#include +#endif + +RCSID("$Id: debug.c,v 1.14 2001/11/29 00:00:30 amb Exp $"); + +unsigned int debuglvl = ~0; +static FILE *debug_output_channel[DEBUG_NUM_FACILITIES]; + +#ifdef MACINTOSH +int ffs( int val ) +{ + int i = 0; + for( i = 0; i < 32; i++ ) + { + if( val & ( 1 << i ) ) + return i+1; + } + return 0; +} +#endif + +void vdebug(int dtype, const char *fmt, va_list ap) +{ + int keep_errno; + char msgbuff[8192]; + + /* errno could be changed by vsprintf or perror */ + keep_errno = errno; + + if (debuglvl & dtype) + { + FILE * channel = debug_output_channel[ffs(dtype)]; + + if (!channel) + channel = stderr; + +#ifdef MACINTOSH + vsprintf(msgbuff, fmt, ap); +#else + vsnprintf(msgbuff, sizeof(msgbuff), fmt, ap); +#endif + + /* DEBUG_ERROR (aka DEBUG_SYSERROR) */ + if (dtype == DEBUG_ERROR) + { + const char * errmsg = ""; + +#ifndef MACINTOSH + errmsg = strerror(errno); +#endif + + fprintf(channel, "%s: %s\n", msgbuff, errmsg); + } + else + fprintf(channel, "%s\n", msgbuff); + + fflush(channel); +#ifdef _WIN32 + if (dtype == DEBUG_SYSERROR || dtype == DEBUG_APPERROR) + MessageBox(NULL, msgbuff, "Application Error", MB_OK); +#endif + } + + errno = keep_errno; +} + +void vmdebug(int dtype, const char * fmt, va_list ap) +{ + FILE * chn[DEBUG_NUM_FACILITIES]; + int i; + + memcpy(chn, debug_output_channel, sizeof(FILE*) * DEBUG_NUM_FACILITIES); + + for (i = 0; i < DEBUG_NUM_FACILITIES; i++) + if (chn[i] == NULL) + chn[i] = stderr; + + for (i = 0; i < DEBUG_NUM_FACILITIES; i++) + { + if ((dtype & (1 << i)) && chn[i]) + { + + if (debuglvl & (1 << i)) + { + int j; + + vdebug(1 << i, fmt, ap); + + for (j = i + 1; j < DEBUG_NUM_FACILITIES; j++) + if (chn[j] == chn[i]) + chn[j] = NULL; + } + } + } +} + +/* FIXME: use actual debug output core routine vdebug... */ +void hexdump(const char *ptr, int size, const char *fmt, ...) +{ + static char hexbuff[49]; + static char printbuff[17]; + int count = 0; + va_list ap; + + if ( !debuglvl & DEBUG_STATUS ) + return; + + va_start(ap, fmt); + + /* print the heading/banner */ + vdebug(DEBUG_STATUS, fmt, ap); + + memset(hexbuff, 0, 49); + memset(printbuff, 0, 17); + + while (size--) + { + sprintf(hexbuff + (count*3), "%02x ", (int)*((unsigned char *)ptr)); + + if (isprint(*ptr)) + printbuff[count] = *ptr; + else + printbuff[count] = '.'; + + ptr++; + + if ( count++ == 15 ) + { + count = 0; + debug(DEBUG_STATUS, "%s %s", hexbuff, printbuff); + memset(hexbuff, 0, 49); + memset(printbuff, 0, 17); + } + } + + if ( count > 0 ) { + while ( count % 16 != 0 ) { + sprintf(hexbuff + (count * 3), "xx "); + printbuff[count++] = '.'; + } + debug(DEBUG_STATUS, "%s %s", hexbuff, printbuff); + } + + va_end(ap); +} + +void +to_hex( char* dest, const char* src, size_t n ) +{ + while ( n-- ) + { + sprintf( dest, "%02x ", (int)*((unsigned char *)src)); + dest += 3; + src++; + } + + *dest = 0; +} + +void debug_set_error_file(FILE *f) +{ + int i; + for (i = 0; i < DEBUG_NUM_FACILITIES; i++) + debug_output_channel[i] = f; +} + +void debug_set_error_facility(int fac, FILE * f) +{ + int i; + + for (i = 0; i < DEBUG_NUM_FACILITIES; i++) + if (!debug_output_channel[i]) + debug_output_channel[i] = stderr; + + debug_output_channel[ffs(fac)] = f; +} diff --git a/cbtcommon/debug.h b/cbtcommon/debug.h new file mode 100644 index 0000000..2ede381 --- /dev/null +++ b/cbtcommon/debug.h @@ -0,0 +1,75 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifndef _DEBUG_H +#define _DEBUG_H + +#include +#include +#ifndef MACINTOSH +#include +#endif + +#include "inline.h" + +#define DEBUG_NUM_FACILITIES 32 /* should be 64 on 64bit CPU... */ +#define DEBUG_SYSERROR 1 /* same as DEBUG_ERROR, but here for clarity */ +#define DEBUG_ERROR 1 +#define DEBUG_STATUS 2 +#define DEBUG_TCP 4 +#define DEBUG_SIGNALS 8 +#define DEBUG_APPERROR 16 +#define DEBUG_APPMSG1 32 +#define DEBUG_APPMSG2 64 +#define DEBUG_APPMSG3 128 +#define DEBUG_APPMSG4 256 +#define DEBUG_APPMSG5 512 +#define DEBUG_LIBERROR 1024 +#define DEBUG_LIBSTATUS 2048 + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern unsigned int debuglvl; + +void hexdump( const char *ptr, int size, const char *fmt, ... ); +void vdebug(int dtype, const char *fmt, va_list); +void vmdebug(int dtype, const char *fmt, va_list); +void to_hex( char* dest, const char* src, size_t n ); +void debug_set_error_file(FILE *); +void debug_set_error_facility(int mask, FILE *); + +static INLINE void debug(unsigned int dtype, const char *fmt, ...) +{ + va_list ap; + + if (!(debuglvl & dtype)) + return; + + va_start(ap, fmt); + vdebug(dtype, fmt, ap); + va_end(ap); +} + +static INLINE void mdebug(unsigned int dtype, const char *fmt, ...) +{ + va_list ap; + + if (!(debuglvl & dtype)) + return; + + va_start(ap, fmt); + vmdebug(dtype, fmt, ap); + va_end(ap); +} + +#ifdef __cplusplus +} +#endif + + +#endif /* DEBUG_H */ diff --git a/cbtcommon/hash.c b/cbtcommon/hash.c new file mode 100644 index 0000000..ddc081b --- /dev/null +++ b/cbtcommon/hash.c @@ -0,0 +1,272 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#include +#include +#include + +#include "debug.h" +#include "hash.h" +#include "rcsid.h" + +RCSID("$Id: hash.c,v 1.6 2003/05/07 15:42:38 david Exp $"); + +#define HASH_CONST 37 + +static unsigned int hash_string(const char *); +static struct hash_entry *scan_list(struct list_head *, const char *); +static struct hash_entry *get_hash_entry(struct hash_table *tbl, const char *key); + +struct hash_table *create_hash_table(unsigned int sz) +{ + struct hash_table *tbl; + unsigned int i; + + tbl = (struct hash_table *)malloc(sizeof(*tbl) + sz*sizeof(struct list_head)); + + if (!tbl) + { + debug(DEBUG_APPERROR, "malloc for hash_table failed"); + return NULL; + } + + tbl->ht_size = sz; + tbl->ht_lists = (struct list_head *)(tbl + 1); + tbl->iterator = 0; + + for (i = 0; i < sz; i++) + INIT_LIST_HEAD(&tbl->ht_lists[i]); + + return tbl; +} + +void destroy_hash_table(struct hash_table *tbl, void (*delete_obj)(void *)) +{ + struct list_head *head, *next, *tmp; + struct hash_entry *entry; + int i; + + for (i = 0; i < tbl->ht_size; i++) + { + head = &tbl->ht_lists[i]; + next = head->next; + + while (next != head) + { + tmp = next->next; + entry = list_entry(next, struct hash_entry, he_list); + if (delete_obj) + delete_obj(entry->he_obj); + free(entry); + + next = tmp; + } + } + + free(tbl); +} + +/* FIXME: there is no way for the user of this to determine the difference + * between a put to a new key value and a malloc failure + */ +void *put_hash_object(struct hash_table *tbl, const char *key, void *obj) +{ + void * retval; + put_hash_object_ex(tbl, key, obj, HT_KEYCOPY, NULL, &retval); + return retval; +} + +static struct hash_entry *get_hash_entry(struct hash_table *tbl, const char *key) +{ + struct list_head *head; + struct hash_entry *entry; + unsigned int hash; + + hash = hash_string(key) % tbl->ht_size; + head = &tbl->ht_lists[hash]; + entry = scan_list(head, key); + + return entry; +} + +void *get_hash_object(struct hash_table *tbl, const char *key) +{ + struct hash_entry *entry = get_hash_entry(tbl, key); + return (entry) ? entry->he_obj : NULL; +} + +void *remove_hash_object(struct hash_table *tbl, const char *key) +{ + struct hash_entry *entry = get_hash_entry(tbl, key); + void *retval = NULL; + + if (entry) + { + list_del(&entry->he_list); + retval = entry->he_obj; + free(entry); + } + + return retval; +} + +static unsigned int hash_string(register const char *key) +{ + register unsigned int hash = 0; + + while(*key) + hash = hash * HASH_CONST + *key++; + + return hash; +} + +static struct hash_entry *scan_list(struct list_head *head, const char *key) +{ + struct list_head *next = head->next; + struct hash_entry *entry; + + while (next != head) + { + entry = list_entry(next, struct hash_entry, he_list); + if (strcmp(entry->he_key, key) == 0) + return entry; + + next = next->next; + } + + return NULL; +} + +void reset_hash_iterator(struct hash_table *tbl) +{ + tbl->iterator = 0; + tbl->iterator_ptr = NULL; +} + +struct hash_entry *next_hash_entry(struct hash_table *tbl) +{ + while( tbl->iterator < tbl->ht_size ) + { + struct list_head *head = &tbl->ht_lists[ tbl->iterator ]; + + if( tbl->iterator_ptr == NULL ) + tbl->iterator_ptr = head->next; + + if( tbl->iterator_ptr != head ) + { + struct list_head *tmp = tbl->iterator_ptr; + tbl->iterator_ptr = tbl->iterator_ptr->next; + return( list_entry( tmp, struct hash_entry, he_list ) ); + } + + else + { + tbl->iterator++; + tbl->iterator_ptr = NULL; + } + } + + return( NULL ); +} + +int put_hash_object_ex(struct hash_table *tbl, const char *key, void *obj, int copy, + char ** oldkey, void ** oldobj) +{ + struct list_head *head; + struct hash_entry *entry; + unsigned int hash; + int retval = 0; + + /* FIXME: how can get_hash_entry be changed to be usable here? + * we need the value of head later if the entry is not found... + */ + hash = hash_string(key) % tbl->ht_size; + head = &tbl->ht_lists[hash]; + entry = scan_list(head, key); + + if (entry) + { + if (oldkey) + *oldkey = entry->he_key; + if (oldobj) + *oldobj = entry->he_obj; + + /* if 'copy' is set, then we already have an exact + * private copy of the key (by definition of having + * found the match in scan_list) so we do nothing. + * if !copy, then we can simply assign the new + * key + */ + if (!copy) + entry->he_key = (char*)key; /* discard the const */ + entry->he_obj = obj; + } + else + { + size_t s = sizeof(*entry); + + if (oldkey) + *oldkey = NULL; + if (oldobj) + *oldobj = NULL; + + if (copy) + s += strlen(key) + 1; + + entry = (struct hash_entry *)malloc(s); + + if (!entry) + { + debug(DEBUG_APPERROR,"malloc failed put_hash_object key='%s'",key); + retval = -1; + } + else + { + if (copy) + { + entry->he_key = (char *)(entry + 1); + strcpy(entry->he_key, key); + } + else + { + entry->he_key = (char*)key; /* discard the const */ + } + + entry->he_obj = obj; + + list_add(&entry->he_list, head); + } + } + + return retval; +} + +void destroy_hash_table_ex(struct hash_table *tbl, + void (*delete_entry)(const void *, char *, void *), + const void * cookie) +{ + struct list_head *head, *next, *tmp; + struct hash_entry *entry; + int i; + + for (i = 0; i < tbl->ht_size; i++) + { + head = &tbl->ht_lists[i]; + next = head->next; + + while (next != head) + { + tmp = next->next; + entry = list_entry(next, struct hash_entry, he_list); + if (delete_entry) + delete_entry(cookie, entry->he_key, entry->he_obj); + free(entry); + + next = tmp; + } + } + + free(tbl); +} diff --git a/cbtcommon/hash.h b/cbtcommon/hash.h new file mode 100644 index 0000000..797e3b3 --- /dev/null +++ b/cbtcommon/hash.h @@ -0,0 +1,52 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifndef _COMMON_HASH_H +#define _COMMON_HASH_H + +#include "list.h" + +struct hash_entry +{ + char *he_key; + void *he_obj; + struct list_head he_list; +}; + +struct hash_table +{ + int ht_size; + struct list_head *ht_lists; + int iterator; + struct list_head *iterator_ptr; +}; + +enum +{ + HT_NO_KEYCOPY, + HT_KEYCOPY +}; + +#ifdef __cplusplus +extern "C" { +#endif + +struct hash_table *create_hash_table(unsigned int sz); +void destroy_hash_table(struct hash_table *tbl, void (*delete_obj)(void *)); +void *put_hash_object(struct hash_table *tbl, const char *key, void *obj); +void *get_hash_object(struct hash_table *tbl, const char *key); +void *remove_hash_object(struct hash_table *tbl, const char *key); + +int put_hash_object_ex(struct hash_table *tbl, const char *key, void *obj, int, char **, void **); +void destroy_hash_table_ex(struct hash_table *tbl, void (*delete_entry)(const void *, char *, void *), const void *); + +void reset_hash_iterator(struct hash_table *tbl); +struct hash_entry *next_hash_entry(struct hash_table *tbl); + +#ifdef __cplusplus +} +#endif + +#endif /* _COMMON_HASH_H */ diff --git a/cbtcommon/inline.h b/cbtcommon/inline.h new file mode 100644 index 0000000..776ef26 --- /dev/null +++ b/cbtcommon/inline.h @@ -0,0 +1,23 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifndef UTIL_INLINE_H +#define UTIL_INLINE_H + +#ifdef __GNUC__ +#define INLINE __inline__ +#endif + +#ifdef WIN32 +#define INLINE __inline +#endif + +/* INLINE of last resort... heh */ + +#ifndef INLINE +#define INLINE /* void */ +#endif + +#endif diff --git a/cbtcommon/list.h b/cbtcommon/list.h new file mode 100644 index 0000000..4ee245d --- /dev/null +++ b/cbtcommon/list.h @@ -0,0 +1,112 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifndef _COMMON_LIST_H +#define _COMMON_LIST_H + +/* + * Stolen from linux-2.1.131 + * All comments from the original source unless otherwise noted + * Added: the CLEAR_LIST_NODE macro + */ + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +#include "inline.h" + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD(name) \ + struct list_head name = { &name, &name } + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +#define CLEAR_LIST_NODE(ptr) do { \ + (ptr)->next = NULL; (ptr)->prev = NULL; \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static INLINE void __list_add(struct list_head *li, + struct list_head * prev, + struct list_head * next) +{ + next->prev = li; + li->next = next; + li->prev = prev; + prev->next = li; +} + +/* + * Insert a new entry after the specified head.. + */ +static INLINE void list_add(struct list_head *li, struct list_head *head) +{ + __list_add(li, head, head->next); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static INLINE void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +static INLINE void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +static INLINE int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/* + * Splice in "list" into "head" + */ +static INLINE void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +#endif /* _COMMON_LIST_H */ diff --git a/cbtcommon/rcsid.h b/cbtcommon/rcsid.h new file mode 100644 index 0000000..b85b6fb --- /dev/null +++ b/cbtcommon/rcsid.h @@ -0,0 +1,21 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifndef _COMMON_RCSID_H +#define _COMMON_RCSID_H + +/* RCS Id macro (complements of bod@compusol.com.au (Brendan O'Dea)) */ +#ifdef lint +# define RCSID(i) +#else /* lint */ +# ifdef __GNUC__ +# define ATTRIB_UNUSED __attribute__ ((unused)) +# else /* __GNUC__ */ +# define ATTRIB_UNUSED +# endif /* __GNUC__ */ +# define RCSID(i) static char const *rcsid ATTRIB_UNUSED = (i) +#endif /* lint */ + +#endif /* _COMMON_RCSID_H */ diff --git a/cbtcommon/sio.c b/cbtcommon/sio.c new file mode 100644 index 0000000..a9faf81 --- /dev/null +++ b/cbtcommon/sio.c @@ -0,0 +1,87 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#include + +#ifdef WIN32 +#include +#else +#include +#endif + +#include + +#include "sio.h" +#include "rcsid.h" + +RCSID("$Id: sio.c,v 1.5 2001/10/25 18:36:11 adam Exp $"); + +ssize_t readn(int fd, void *buf, size_t len) +{ + + int nleft,nread; + + nleft = len; + + while (nleft > 0) + { + nread = read(fd,buf,nleft); + + /* there is an issue which EINTR which could leave us a bit haywire + * if we get a signal after having read some bytes. special handling + * N.B: we *do* return EINTR if no data has been read yet (thanks Karl) + */ + if (nread < 0) + { + if (errno == EINTR && nleft != (int)len) + continue; + else + return (nread); + } + else if (nread == 0) + break; + + nleft -= nread; + + if (nleft) + buf = ((char *)buf) + nread; + } + return (len - nleft); +} + +ssize_t writen(int fd, const void *buf, size_t len) +{ + + int nleft, nwritten; + + nleft = len; + + while (nleft > 0) + { + nwritten = write(fd,buf,nleft); + + /* there is an issue with EINTR if we have already written + a few bytes! return if we have not written any yet */ + if (nwritten < 0 && errno == EINTR) + { + if (nleft == (int)len) + return nwritten; + + continue; + } + + + if (nwritten <= 0) + return nwritten; + + nleft -= nwritten; + + if (nleft) + buf = ((char *)buf) + nwritten; + } + + return (len - nleft); +} + diff --git a/cbtcommon/sio.h b/cbtcommon/sio.h new file mode 100644 index 0000000..f6f2978 --- /dev/null +++ b/cbtcommon/sio.h @@ -0,0 +1,29 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifndef _SIO_H +#define _SIO_H + +/* include for typedefs */ +#ifdef WIN32 +#include +typedef int ssize_t; +#else +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif +/* these are W.R.Stevens' famous io routines to read or write bytes to fd */ +ssize_t readn(int, void *, size_t); +ssize_t writen(int, const void *, size_t); + +#ifdef __cplusplus +} +#endif + +#endif /* _SIO_H */ diff --git a/cbtcommon/tcpsocket.c b/cbtcommon/tcpsocket.c new file mode 100644 index 0000000..27cc13a --- /dev/null +++ b/cbtcommon/tcpsocket.c @@ -0,0 +1,257 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifdef SOLARIS +#include +#else +#include +#endif + +#ifdef WIN32 +#include +#else /* not windows */ +#include +#include +#include +#include +#include +#include + +#ifdef SOLARIS +#include +#endif + +#endif /* if windows */ + +#include "tcpsocket.h" +#include "debug.h" +#include "rcsid.h" +#ifdef WIN32 +#include "win32fd.h" +#endif + +RCSID("$Id: tcpsocket.c,v 1.6 1999/12/27 20:35:34 david Exp $"); + +int +tcp_create_socket(int reuse_addr) +{ + int retval; + int yes = 1; + + if ((retval = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + debug(DEBUG_ERROR, "tcp: can't create socket"); + } + + if (reuse_addr) + { + setsockopt( retval, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(int)); + } + + debug(DEBUG_TCP, "tcp: socket created"); +#ifdef WIN32 + return get_fd(retval, WIN32_SOCKET); +#else + return retval; +#endif +} + +int +tcp_bind_and_listen(int sockfd, unsigned short tcp_port) +{ + struct sockaddr_in addr; + + memset((char *) &addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(tcp_port); + +#ifdef WIN32 + sockfd = win32_file_table[sockfd].win32id; +#endif + + if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + debug(DEBUG_ERROR, "tcp: can't bind to socket"); + return -1; + } + + + if (listen(sockfd, LISTEN_QUEUE_SIZE) < 0) + { + debug(DEBUG_ERROR, "tcp: can't listen on socket"); + return -1; + } + + debug(DEBUG_TCP, "tcp: socket bound and listening"); + + return 0; +} + +int +tcp_accept_connection(int sockfd) +{ + struct sockaddr_in remaddr; + int addrlen; + int retval; + +#ifdef WIN32 + sockfd = win32_file_table[sockfd].win32id; +#endif + + addrlen = sizeof(struct sockaddr_in); + +#ifdef WIN32 + if ((retval = accept(sockfd, (struct sockaddr *) &remaddr, &addrlen)) == INVALID_SOCKET) + { + debug(DEBUG_APPERROR, "tcp: error accepting connection"); + return -1; + } +#else + if ((retval = accept(sockfd, (struct sockaddr *) &remaddr, &addrlen)) < 0) + { + if (errno != EINTR ) + debug(DEBUG_ERROR, "tcp: error accepting connection"); + + return -1; + } +#endif + + debug(DEBUG_TCP, "tcp: got connection (fd=%d)", retval); + + return retval; +} + +unsigned int +tcp_get_client_ip(int fd) +{ + struct sockaddr_in remaddr; + int addrlen; + int retval; + unsigned int saddr; + +#ifdef WIN32 + fd = win32_file_table[fd].win32id; +#endif + + addrlen = sizeof(struct sockaddr_in); + + if ((retval = getpeername(fd, (struct sockaddr *) &remaddr, &addrlen)) < 0) + { + debug(DEBUG_ERROR, "tcp: error getting remote's ip address"); + return 0; + } + + saddr = ntohl(remaddr.sin_addr.s_addr); + + return saddr; +} + +int +tcp_connect(int sockfd, const char *rem_addr, unsigned short port) +{ + struct sockaddr_in addr; + int addrlen; + long ipno; + +#ifdef WIN32 + sockfd = win32_file_table[sockfd].win32id; +#endif + + if ( convert_address(&ipno , rem_addr) < 0 ) + { + return -1; + } + + addrlen = sizeof(struct sockaddr_in); + + memset((char *) &addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = ipno; + addr.sin_port = htons(port); + + if (connect(sockfd, (struct sockaddr *)&addr, addrlen) < 0) + { + debug(DEBUG_ERROR, "connect error"); + return -1; + } + + debug(DEBUG_STATUS, "tcp: connection established on port %d", port); + return 0; +} + +int +convert_address(long *dest, const char *addr_str) +{ +#ifdef LINUX + struct in_addr ip; +#endif + int retval = 0; + char errstr[256]; + + /* first try converting "numbers and dots" notation */ +#ifdef LINUX + if ( inet_aton(addr_str, &ip) ) + { + memcpy(dest, &ip.s_addr, sizeof(ip.s_addr)); + } +#else + if ( (*dest = inet_addr(addr_str)) != -1) + { + /* nothing */ + } +#endif + else /* if it fails, do a gethostbyname() */ + { + struct hostent *host; + if ((host = gethostbyname(addr_str)) == NULL) + { + switch(h_errno) + { + case HOST_NOT_FOUND: + strcpy(errstr, "HOST_NOT_FOUND"); + break; + + case NO_ADDRESS: + strcpy(errstr, "NO_ADDRESS"); + break; + + case NO_RECOVERY: + strcpy(errstr, "NO_RECOVERY"); + break; + + case TRY_AGAIN: + strcpy(errstr, "TRY_AGAIN"); + break; + } + + debug(DEBUG_ERROR, "gethostbyname failed for %s: ", addr_str, errstr); + + retval = -1; + } + + memcpy(dest, host->h_addr_list[0], sizeof(unsigned long)); + } + + + return retval; +} + +int tcp_get_local_address(int sockfd, unsigned int *ip, unsigned short *port) +{ + struct sockaddr_in addr; + int addrlen = sizeof(struct sockaddr_in); + + if(getsockname(sockfd, (struct sockaddr *)&addr, &addrlen) < 0) + { + debug(DEBUG_SYSERROR, "getsockname failed" ); + return -1; + } + + *ip = ntohl( addr.sin_addr.s_addr ); + *port = ntohs( addr.sin_port ); + + return 0; +} diff --git a/cbtcommon/tcpsocket.h b/cbtcommon/tcpsocket.h new file mode 100644 index 0000000..ac606a5 --- /dev/null +++ b/cbtcommon/tcpsocket.h @@ -0,0 +1,33 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifndef _TCPSOCKET_H +#define _TCPSOCKET_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifndef LISTEN_QUEUE_SIZE +#define LISTEN_QUEUE_SIZE 5 +#endif + +#define REUSE_ADDR 1 +#define NO_REUSE_ADDR 0 + +int tcp_create_socket(int reuse_addr); +int tcp_bind_and_listen(int sockfd, unsigned short tcpport); +int tcp_accept_connection(int sockfd); +unsigned int tcp_get_client_ip(int fd); +int tcp_connect(int sockfd, const char *rem_addr, unsigned short port); +int convert_address(long *dest, const char *addr_str); +int tcp_get_local_address(int sockfd, unsigned int *, unsigned short *); + +#ifdef __cplusplus +} +#endif + +#endif /* TCPSOCKET_H */ diff --git a/cbtcommon/text_util.c b/cbtcommon/text_util.c new file mode 100644 index 0000000..052a94b --- /dev/null +++ b/cbtcommon/text_util.c @@ -0,0 +1,317 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +/** + * Copyright (c) 1998 Cobite, Inc. All Rights Reserved. + * @author Karl LaRocca + * @created Fri Nov 6 14:33:29 1998 + * @version $Revision: 1.9 $$Date: 2001/10/25 18:36:11 $ + */ +#include +#include +#include +#include + +#include "text_util.h" +#include "rcsid.h" + +RCSID("$Id: text_util.c,v 1.9 2001/10/25 18:36:11 adam Exp $"); + +char* +chop( char* src ) +{ + char* p = src + strlen(src) - 1; + + while( p >= src ) + { + if ( *p == '\n' || *p == '\r' ) + { + *p-- = 0; + } + + else + { + break; + } + } + + return( src ); +} + +char* +digits( char* src ) +{ + char* start = src; + char* check = src; + + while( *check ) + { + if ( isdigit( *check ) ) + { + *start++ = *check; + } + + check++; + } + + *start = 0; + + return( src ); +} + +char* +lower_case( char* src ) +{ + char* p = src; + + while( *p ) + { + *p = tolower( *p ); + p++; + } + + return( src ); +} + +char* +reverse( char* src ) +{ + int i; + int len = strlen( src ); + char tmp; + + for( i = len / 2; --i >= 0; ) + { + tmp = src[ i ]; + src[ i ] = src[ len - i - 1 ]; + src[ len - i - 1 ] = tmp; + } + + return( src ); +} + +char* +trim( char* src ) +{ + char *p = src + strlen(src) - 1; + + while( p >= src && isspace(*p) ) + *p-- = '\0'; + + return src; +} + +char* +upper_case( char* src ) +{ + char* p = src; + + while( *p ) + { + *p = toupper(*p); + p++; + } + + return( src ); +} + +int +strrcmp( const char* haystack, const char* needle ) +{ + int hlen = strlen( haystack ); + int nlen = strlen( needle ); + if( hlen < nlen ) + return( -1 ); + else + return( strcmp( haystack + hlen - nlen, needle ) ); +} + +/* + * Finding a - anywhere in the string makes it money negative. + * all characters other than digits, '-', and '.' are ignored, so: + * ab36-.g98 = -36.98 + * This is fair, I think, if we don't want to reject anything as + * improperly formatted. + */ +long +money2cents( const char* money ) +{ + long retval = 0; + int decimal_places = -1; + int neg = 0; + + while( *money && decimal_places < 2 ) + { + if ( isdigit( *money ) ) + { + if ( decimal_places >= 0 ) + decimal_places++; + + retval *= 10; + retval += (*money) - '0'; + } + + else if ( *money == '.' ) + decimal_places = 0; + + else if ( *money == '-' ) + neg = 1; + + money++; + } + + if ( decimal_places == 1 ) + retval *= 10; + + else if ( decimal_places <= 0 ) + retval *= 100; + + return( neg ? -retval : retval ); +} + +const char* +cents2money( long cents ) +{ + static char buff[ 64 ]; + int idx = 0; + char* d = buff; + + if ( cents == 0 ) + { + strcpy( buff, "0.00" ); + } + + else if ( cents < 100 ) + { + sprintf( buff, "0.%2.2ld", cents ); + } + + else + { + while( cents > 0 ) + { + *d++ = '0' + ( cents % 10 ); + cents = cents / 10; + + if ( idx == 1 ) + { + *d++ = '.'; + } + + else if ( cents > 0 && ( idx - 1 ) % 3 == 0 ) + { + *d++ = ','; + } + + idx++; + } + + *d++ = 0; + + reverse( buff ); + } + + return( buff ); +} + +void trim_zeros_after_decimal( char* src ) +{ + char * end = src + strlen( src ) - 1; + + while( end != src ) + { + if( *end == '0' ) + *end = 0; + else if( *end == '.' ) + { + *end = 0; + break; + } + else + break; + + end--; + } +} + +#ifdef linux +extern void *memfrob(void *, size_t); +#else +static void * memfrob(void * mem, size_t len) +{ + size_t i; + char *c = (char *)mem; + + for (i = 0; i < len; i++) + { + *c = *c ^ 42; + c++; + } + + return mem; +} +#endif + +// simple functions to obfuscate strings in a binary +char* frobstr( char* src ) +{ + char* retval = (char*)malloc( strlen(src) * 2 + 1 ); + + memfrob( src, strlen( src ) ); + str2hex( retval, src, 0 ); + memfrob( src, strlen( src ) ); + + return( retval ); +} + +char* unfrobstr( char* src ) +{ + int slen = strlen( src ) / 2; + char* retval = (char*)malloc( slen + 1 ); + + hex2str( retval, src, 0 ); + memfrob( retval, slen ); + + return( retval ); +} + +void str2hex( char* dest, const char* src, int slen ) +{ + int i; + char* p = dest; + + if( slen == 0 ) + slen = strlen( src ); + + for ( i = 0; i < slen; i++ ) + { + sprintf( p, "%02x", src[i] ); + p += 2; + } + + *p = 0; +} + +void hex2str( char* dest, const char* src, int slen ) +{ + const char* p = src; + int i; + unsigned int v; + + if( slen == 0 ) + slen = strlen( src ); + + slen /= 2; + + for( i = 0; i < slen; i++ ) + { + sscanf( p, "%02x", &v ); + dest[i] = (char)v; + p += 2; + } + + dest[ slen ] = 0; +} + diff --git a/cbtcommon/text_util.h b/cbtcommon/text_util.h new file mode 100644 index 0000000..b22477b --- /dev/null +++ b/cbtcommon/text_util.h @@ -0,0 +1,43 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +/** + * Copyright (c) 1998 Cobite, Inc. All Rights Reserved. + * @author Karl LaRocca + * @created Fri Nov 6 14:48:04 1998 + * @version $Revision: 1.4 $$Date: 2001/10/25 18:36:11 $ + */ +#ifndef _TEXT_UTIL_H +#define _TEXT_UTIL_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +char* chop( char* src ); +char* digits( char* src ); +char* lower_case( char* src ); +char* reverse( char* src ); +char* trim( char* src ); +void trim_zeros_after_decimal( char* src ); +char* upper_case( char* src ); +int strrcmp( const char* haystack, const char* needle ); + +const char* cents2money( long cents ); +long money2cents( const char* money ); + +// these two allocate returned memory, so be sure to free it... +char* frobstr( char* src ); +char* unfrobstr( char* src ); + +void str2hex( char* dest, const char* src, int slen ); +void hex2str( char* dest, const char* src, int slen ); + +#ifdef __cplusplus +} +#endif + +#endif /* _TEXT_UTIL_H */ diff --git a/cvs_direct.c b/cvs_direct.c new file mode 100644 index 0000000..920487d --- /dev/null +++ b/cvs_direct.c @@ -0,0 +1,925 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cvs_direct.h" +#include "util.h" + +#define RD_BUFF_SIZE 4096 + +struct _CvsServerCtx +{ + int read_fd; + int write_fd; + char root[PATH_MAX]; + + int is_pserver; + + /* buffered reads from descriptor */ + char read_buff[RD_BUFF_SIZE]; + char * head; + char * tail; + + int compressed; + z_stream zout; + z_stream zin; + + /* when reading compressed data, the compressed data buffer */ + char zread_buff[RD_BUFF_SIZE]; +}; + +static void get_cvspass(char *, const char *); +static void send_string(CvsServerCtx *, const char *, ...); +static int read_response(CvsServerCtx *, const char *); +static void ctx_to_fp(CvsServerCtx * ctx, FILE * fp); +static int read_line(CvsServerCtx * ctx, char * p); + +static CvsServerCtx * open_ctx_pserver(CvsServerCtx *, const char *); +static CvsServerCtx * open_ctx_forked(CvsServerCtx *, const char *); + +CvsServerCtx * open_cvs_server(char * p_root, int compress) +{ + CvsServerCtx * ctx = (CvsServerCtx*)malloc(sizeof(*ctx)); + char root[PATH_MAX]; + char * p = root, *tok; + + if (!ctx) + return NULL; + + ctx->head = ctx->tail = ctx->read_buff; + ctx->read_fd = ctx->write_fd = -1; + ctx->compressed = 0; + ctx->is_pserver = 0; + + if (compress) + { + memset(&ctx->zout, 0, sizeof(z_stream)); + memset(&ctx->zin, 0, sizeof(z_stream)); + + /* + * to 'prime' the reads, make it look like there was output + * room available (i.e. we have processed all pending compressed + * data + */ + ctx->zin.avail_out = 1; + + if (deflateInit(&ctx->zout, compress) != Z_OK) + { + free(ctx); + return NULL; + } + + if (inflateInit(&ctx->zin) != Z_OK) + { + deflateEnd(&ctx->zout); + free(ctx); + return NULL; + } + } + + strcpy(root, p_root); + + tok = strsep(&p, ":"); + + /* if root string looks like :pserver:... then the first token will be empty */ + if (strlen(tok) == 0) + { + char * method = strsep(&p, ":"); + if (strcmp(method, "pserver") == 0) + { + ctx = open_ctx_pserver(ctx, p); + } + else if (strstr("local:ext:fork:server", method)) + { + /* handle all of these via fork, even local */ + ctx = open_ctx_forked(ctx, p); + } + else + { + debug(DEBUG_APPERROR, "cvs_direct: unsupported cvs access method: %s", method); + free(ctx); + ctx = NULL; + } + } + else + { + ctx = open_ctx_forked(ctx, p_root); + } + + if (ctx) + { + char buff[BUFSIZ]; + + send_string(ctx, "Root %s\n", ctx->root); + + /* this is taken from 1.11.1p1 trace - but with Mbinary removed. we can't handle it (yet!) */ + send_string(ctx, "Valid-responses ok error Valid-requests Checked-in New-entry Checksum Copy-file Updated Created Update-existing Merged Patched Rcs-diff Mode Mod-time Removed Remove-entry Set-static-directory Clear-static-directory Set-sticky Clear-sticky Template Set-checkin-prog Set-update-prog Notified Module-expansion Wrapper-rcsOption M E F\n", ctx->root); + + send_string(ctx, "valid-requests\n"); + + /* check for the commands we will issue */ + read_line(ctx, buff); + if (strncmp(buff, "Valid-requests", 14) != 0) + { + debug(DEBUG_APPERROR, "cvs_direct: bad response to valid-requests command"); + close_cvs_server(ctx); + return NULL; + } + + if (!strstr(buff, " version") || + !strstr(buff, " rlog") || + !strstr(buff, " rdiff") || + !strstr(buff, " diff") || + !strstr(buff, " co")) + { + debug(DEBUG_APPERROR, "cvs_direct: cvs server too old for cvs_direct"); + close_cvs_server(ctx); + return NULL; + } + + read_line(ctx, buff); + if (strcmp(buff, "ok") != 0) + { + debug(DEBUG_APPERROR, "cvs_direct: bad ok trailer to valid-requests command"); + close_cvs_server(ctx); + return NULL; + } + + /* this is myterious but 'mandatory' */ + send_string(ctx, "UseUnchanged\n"); + + if (compress) + { + send_string(ctx, "Gzip-stream %d\n", compress); + ctx->compressed = 1; + } + + debug(DEBUG_APPMSG1, "cvs_direct initialized to CVSROOT %s", ctx->root); + } + + return ctx; +} + +static CvsServerCtx * open_ctx_pserver(CvsServerCtx * ctx, const char * p_root) +{ + char root[PATH_MAX]; + char full_root[PATH_MAX]; + char * p = root, *tok, *tok2; + char user[BUFSIZ]; + char server[BUFSIZ]; + char pass[BUFSIZ]; + char port[8]; + + strcpy(root, p_root); + + tok = strsep(&p, ":"); + if (strlen(tok) == 0 || !p) + { + debug(DEBUG_APPERROR, "parse error on third token"); + goto out_free_err; + } + + tok2 = strsep(&tok, "@"); + if (!strlen(tok2) || (!tok || !strlen(tok))) + { + debug(DEBUG_APPERROR, "parse error on user@server in pserver"); + goto out_free_err; + } + + strcpy(user, tok2); + strcpy(server, tok); + + if (*p != '/') + { + tok = strchr(p, '/'); + if (!tok) + { + debug(DEBUG_APPERROR, "parse error: expecting / in root"); + goto out_free_err; + } + + memset(port, 0, sizeof(port)); + memcpy(port, p, tok - p); + + p = tok; + } + else + { + strcpy(port, "2401"); + } + + /* the line from .cvspass is fully qualified, so rebuild */ + snprintf(full_root, PATH_MAX, ":pserver:%s@%s:%s%s", user, server, port, p); + get_cvspass(pass, full_root); + + debug(DEBUG_TCP, "user:%s server:%s port:%s pass:%s full_root:%s", user, server, port, pass, full_root); + + if ((ctx->read_fd = tcp_create_socket(REUSE_ADDR)) < 0) + goto out_free_err; + + ctx->write_fd = dup(ctx->read_fd); + + if (tcp_connect(ctx->read_fd, server, atoi(port)) < 0) + goto out_close_err; + + send_string(ctx, "BEGIN AUTH REQUEST\n"); + send_string(ctx, "%s\n", p); + send_string(ctx, "%s\n", user); + send_string(ctx, "%s\n", pass); + send_string(ctx, "END AUTH REQUEST\n"); + + if (!read_response(ctx, "I LOVE YOU")) + goto out_close_err; + + strcpy(ctx->root, p); + ctx->is_pserver = 1; + + return ctx; + + out_close_err: + close(ctx->read_fd); + out_free_err: + free(ctx); + return NULL; +} + +static CvsServerCtx * open_ctx_forked(CvsServerCtx * ctx, const char * p_root) +{ + char root[PATH_MAX]; + char * p = root, *tok, *tok2, *rep; + char execcmd[PATH_MAX]; + int to_cvs[2]; + int from_cvs[2]; + pid_t pid; + const char * cvs_server = getenv("CVS_SERVER"); + + if (!cvs_server) + cvs_server = "cvs"; + + strcpy(root, p_root); + + /* if there's a ':', it's remote */ + tok = strsep(&p, ":"); + + if (p) + { + const char * cvs_rsh = getenv("CVS_RSH"); + + if (!cvs_rsh) + cvs_rsh = "rsh"; + + tok2 = strsep(&tok, "@"); + + if (tok) + snprintf(execcmd, PATH_MAX, "%s -l %s %s %s server", cvs_rsh, tok2, tok, cvs_server); + else + snprintf(execcmd, PATH_MAX, "%s %s %s server", cvs_rsh, tok2, cvs_server); + + rep = p; + } + else + { + snprintf(execcmd, PATH_MAX, "%s server", cvs_server); + rep = tok; + } + + if (pipe(to_cvs) < 0) + { + debug(DEBUG_SYSERROR, "cvs_direct: failed to create pipe to_cvs"); + goto out_free_err; + } + + if (pipe(from_cvs) < 0) + { + debug(DEBUG_SYSERROR, "cvs_direct: failed to create pipe from_cvs"); + goto out_close_err; + } + + debug(DEBUG_TCP, "forked cmdline: %s", execcmd); + + if ((pid = fork()) < 0) + { + debug(DEBUG_SYSERROR, "cvs_direct: can't fork"); + goto out_close2_err; + } + else if (pid == 0) /* child */ + { + char * argp[4]; + argp[0] = "sh"; + argp[1] = "-c"; + argp[2] = execcmd; + argp[3] = NULL; + + close(to_cvs[1]); + close(from_cvs[0]); + + close(0); + dup(to_cvs[0]); + close(1); + dup(from_cvs[1]); + + execv("/bin/sh",argp); + + debug(DEBUG_APPERROR, "cvs_direct: fatal: shouldn't be reached"); + exit(1); + } + + close(to_cvs[0]); + close(from_cvs[1]); + ctx->read_fd = from_cvs[0]; + ctx->write_fd = to_cvs[1]; + + strcpy(ctx->root, rep); + + return ctx; + + out_close2_err: + close(from_cvs[0]); + close(from_cvs[1]); + out_close_err: + close(to_cvs[0]); + close(to_cvs[1]); + out_free_err: + free(ctx); + return NULL; +} + +void close_cvs_server(CvsServerCtx * ctx) +{ + /* FIXME: some sort of flushing should be done for non-compressed case */ + + if (ctx->compressed) + { + int ret, len; + char buff[BUFSIZ]; + + /* + * there shouldn't be anything left, but we do want + * to send an 'end of stream' marker, (if such a thing + * actually exists..) + */ + do + { + ctx->zout.next_out = buff; + ctx->zout.avail_out = BUFSIZ; + ret = deflate(&ctx->zout, Z_FINISH); + + if ((ret == Z_OK || ret == Z_STREAM_END) && ctx->zout.avail_out != BUFSIZ) + { + len = BUFSIZ - ctx->zout.avail_out; + if (writen(ctx->write_fd, buff, len) != len) + debug(DEBUG_APPERROR, "cvs_direct: zout: error writing final state"); + + //hexdump(buff, len, "cvs_direct: zout: sending unsent data"); + } + } while (ret == Z_OK); + + if ((ret = deflateEnd(&ctx->zout)) != Z_OK) + debug(DEBUG_APPERROR, "cvs_direct: zout: deflateEnd error: %s: %s", + (ret == Z_STREAM_ERROR) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx->zout.msg); + } + + /* we're done writing now */ + debug(DEBUG_TCP, "cvs_direct: closing cvs server write connection %d", ctx->write_fd); + close(ctx->write_fd); + + /* + * if this is pserver, then read_fd is a bi-directional socket. + * we want to shutdown the write side, just to make sure the + * server get's eof + */ + if (ctx->is_pserver) + { + debug(DEBUG_TCP, "cvs_direct: shutdown on read socket"); + if (shutdown(ctx->read_fd, SHUT_WR) < 0) + debug(DEBUG_SYSERROR, "cvs_direct: error with shutdown on pserver socket"); + } + + if (ctx->compressed) + { + int ret = Z_OK, len, eof = 0; + char buff[BUFSIZ]; + + /* read to the 'eof'/'eos' marker. there are two states we + * track, looking for Z_STREAM_END (application level EOS) + * and EOF on socket. Both should happen at the same time, + * but we need to do the read first, the first time through + * the loop, but we want to do one read after getting Z_STREAM_END + * too. so this loop has really ugly exit conditions. + */ + for(;;) + { + /* + * if there's nothing in the avail_in, and we + * inflated everything last pass (avail_out != 0) + * then slurp some more from the descriptor, + * if we get EOF, exit the loop + */ + if (ctx->zin.avail_in == 0 && ctx->zin.avail_out != 0) + { + debug(DEBUG_TCP, "cvs_direct: doing final slurp"); + len = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE); + debug(DEBUG_TCP, "cvs_direct: did final slurp: %d", len); + + if (len <= 0) + { + eof = 1; + break; + } + + /* put the data into the inflate input stream */ + ctx->zin.next_in = ctx->zread_buff; + ctx->zin.avail_in = len; + } + + /* + * if the last time through we got Z_STREAM_END, and we + * get back here, it means we should've gotten EOF but + * didn't + */ + if (ret == Z_STREAM_END) + break; + + ctx->zin.next_out = buff; + ctx->zin.avail_out = BUFSIZ; + + ret = inflate(&ctx->zin, Z_SYNC_FLUSH); + len = BUFSIZ - ctx->zin.avail_out; + + if (ret == Z_BUF_ERROR) + debug(DEBUG_APPERROR, "Z_BUF_ERROR"); + + if (ret == Z_OK && len == 0) + debug(DEBUG_TCP, "cvs_direct: no data out of inflate"); + + if (ret == Z_STREAM_END) + debug(DEBUG_TCP, "cvs_direct: got Z_STREAM_END"); + + if ((ret == Z_OK || ret == Z_STREAM_END) && len > 0) + hexdump(buff, BUFSIZ - ctx->zin.avail_out, "cvs_direct: zin: unread data at close"); + } + + if (ret != Z_STREAM_END) + debug(DEBUG_APPERROR, "cvs_direct: zin: Z_STREAM_END not encountered (premature EOF?)"); + + if (eof == 0) + debug(DEBUG_APPERROR, "cvs_direct: zin: EOF not encountered (premature Z_STREAM_END?)"); + + if ((ret = inflateEnd(&ctx->zin)) != Z_OK) + debug(DEBUG_APPERROR, "cvs_direct: zin: inflateEnd error: %s: %s", + (ret == Z_STREAM_ERROR) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx->zin.msg ? ctx->zin.msg : ""); + } + + debug(DEBUG_TCP, "cvs_direct: closing cvs server read connection %d", ctx->read_fd); + close(ctx->read_fd); + + free(ctx); +} + +static void get_cvspass(char * pass, const char * root) +{ + char cvspass[PATH_MAX]; + const char * home; + FILE * fp; + + pass[0] = 0; + + if (!(home = getenv("HOME"))) + { + debug(DEBUG_APPERROR, "HOME environment variable not set"); + exit(1); + } + + if (snprintf(cvspass, PATH_MAX, "%s/.cvspass", home) >= PATH_MAX) + { + debug(DEBUG_APPERROR, "prefix buffer overflow"); + exit(1); + } + + if ((fp = fopen(cvspass, "r"))) + { + char buff[BUFSIZ]; + int len = strlen(root); + + while (fgets(buff, BUFSIZ, fp)) + { + /* FIXME: what does /1 mean? */ + if (strncmp(buff, "/1 ", 3) != 0) + continue; + + if (strncmp(buff + 3, root, len) == 0) + { + strcpy(pass, buff + 3 + len + 1); + chop(pass); + break; + } + + } + fclose(fp); + } + + if (!pass[0]) + pass[0] = 'A'; +} + +static void send_string(CvsServerCtx * ctx, const char * str, ...) +{ + int len; + char buff[BUFSIZ]; + va_list ap; + + va_start(ap, str); + + len = vsnprintf(buff, BUFSIZ, str, ap); + if (len >= BUFSIZ) + { + debug(DEBUG_APPERROR, "cvs_direct: command send string overflow"); + exit(1); + } + + if (ctx->compressed) + { + char zbuff[BUFSIZ]; + + if (ctx->zout.avail_in != 0) + { + debug(DEBUG_APPERROR, "cvs_direct: zout: last output command not flushed"); + exit(1); + } + + ctx->zout.next_in = buff; + ctx->zout.avail_in = len; + ctx->zout.avail_out = 0; + + while (ctx->zout.avail_in > 0 || ctx->zout.avail_out == 0) + { + int ret; + + ctx->zout.next_out = zbuff; + ctx->zout.avail_out = BUFSIZ; + + /* FIXME: for the arguments before a command, flushing is counterproductive */ + ret = deflate(&ctx->zout, Z_SYNC_FLUSH); + + if (ret == Z_OK) + { + len = BUFSIZ - ctx->zout.avail_out; + + if (writen(ctx->write_fd, zbuff, len) != len) + { + debug(DEBUG_SYSERROR, "cvs_direct: zout: can't write"); + exit(1); + } + } + else + { + debug(DEBUG_APPERROR, "cvs_direct: zout: error %d %s", ret, ctx->zout.msg); + } + } + } + else + { + if (writen(ctx->write_fd, buff, len) != len) + { + debug(DEBUG_SYSERROR, "cvs_direct: can't send command"); + exit(1); + } + } + + debug(DEBUG_TCP, "string: '%s' sent", buff); +} + +static int refill_buffer(CvsServerCtx * ctx) +{ + int len; + + if (ctx->head != ctx->tail) + { + debug(DEBUG_APPERROR, "cvs_direct: refill_buffer called on non-empty buffer"); + exit(1); + } + + ctx->head = ctx->read_buff; + len = RD_BUFF_SIZE; + + if (ctx->compressed) + { + int zlen, ret; + + /* if there was leftover buffer room, it's time to slurp more data */ + do + { + if (ctx->zin.avail_out > 0) + { + if (ctx->zin.avail_in != 0) + { + debug(DEBUG_APPERROR, "cvs_direct: zin: expect 0 avail_in"); + exit(1); + } + zlen = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE); + ctx->zin.next_in = ctx->zread_buff; + ctx->zin.avail_in = zlen; + } + + ctx->zin.next_out = ctx->head; + ctx->zin.avail_out = len; + + /* FIXME: we don't always need Z_SYNC_FLUSH, do we? */ + ret = inflate(&ctx->zin, Z_SYNC_FLUSH); + } + while (ctx->zin.avail_out == len); + + if (ret == Z_OK) + { + ctx->tail = ctx->head + (len - ctx->zin.avail_out); + } + else + { + debug(DEBUG_APPERROR, "cvs_direct: zin: error %d %s", ret, ctx->zin.msg); + exit(1); + } + } + else + { + len = read(ctx->read_fd, ctx->head, len); + ctx->tail = (len <= 0) ? ctx->head : ctx->head + len; + } + + return len; +} + +static int read_line(CvsServerCtx * ctx, char * p) +{ + int len = 0; + while (1) + { + if (ctx->head == ctx->tail) + if (refill_buffer(ctx) <= 0) + return -1; + + *p = *ctx->head++; + + if (*p == '\n') + { + *p = 0; + break; + } + p++; + len++; + } + + return len; +} + +static int read_response(CvsServerCtx * ctx, const char * str) +{ + /* FIXME: more than 1 char at a time */ + char resp[BUFSIZ]; + + if (read_line(ctx, resp) < 0) + return 0; + + debug(DEBUG_TCP, "response '%s' read", resp); + + return (strcmp(resp, str) == 0); +} + +static void ctx_to_fp(CvsServerCtx * ctx, FILE * fp) +{ + char line[BUFSIZ]; + + while (1) + { + read_line(ctx, line); + debug(DEBUG_TCP, "ctx_to_fp: %s", line); + if (memcmp(line, "M ", 2) == 0) + { + if (fp) + fprintf(fp, "%s\n", line + 2); + } + else if (memcmp(line, "E ", 2) == 0) + { + debug(DEBUG_APPMSG1, "%s", line + 2); + } + else if (strncmp(line, "ok", 2) == 0 || strncmp(line, "error", 5) == 0) + { + break; + } + } + + if (fp) + fflush(fp); +} + +void cvs_rdiff(CvsServerCtx * ctx, + const char * rep, const char * file, + const char * rev1, const char * rev2) +{ + /* NOTE: opts are ignored for rdiff, '-u' is always used */ + + send_string(ctx, "Argument -u\n"); + send_string(ctx, "Argument -r\n"); + send_string(ctx, "Argument %s\n", rev1); + send_string(ctx, "Argument -r\n"); + send_string(ctx, "Argument %s\n", rev2); + send_string(ctx, "Argument %s%s\n", rep, file); + send_string(ctx, "rdiff\n"); + + ctx_to_fp(ctx, stdout); +} + +void cvs_rupdate(CvsServerCtx * ctx, const char * rep, const char * file, const char * rev, int create, const char * opts) +{ + FILE * fp; + char cmdbuff[BUFSIZ]; + + snprintf(cmdbuff, BUFSIZ, "diff %s %s /dev/null %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s/%s|g'", + opts, create?"":"-", create?"-":"", create?"2":"1", rep, file); + + debug(DEBUG_TCP, "cmdbuff: %s", cmdbuff); + + if (!(fp = popen(cmdbuff, "w"))) + { + debug(DEBUG_APPERROR, "cvs_direct: popen for diff failed: %s", cmdbuff); + exit(1); + } + + send_string(ctx, "Argument -p\n"); + send_string(ctx, "Argument -r\n"); + send_string(ctx, "Argument %s\n", rev); + send_string(ctx, "Argument %s/%s\n", rep, file); + send_string(ctx, "co\n"); + + ctx_to_fp(ctx, fp); + + pclose(fp); +} + +static int parse_patch_arg(char * arg, char ** str) +{ + char *tok, *tok2 = ""; + tok = strsep(str, " "); + if (!tok) + return 0; + + if (!*tok == '-') + { + debug(DEBUG_APPERROR, "diff_opts parse error: no '-' starting argument: %s", *str); + return 0; + } + + /* if it's not 'long format' argument, we can process it efficiently */ + if (tok[1] == '-') + { + debug(DEBUG_APPERROR, "diff_opts parse_error: long format args not supported"); + return 0; + } + + /* see if command wants two args and they're separated by ' ' */ + if (tok[2] == 0 && strchr("BdDFgiorVxYz", tok[1])) + { + tok2 = strsep(str, " "); + if (!tok2) + { + debug(DEBUG_APPERROR, "diff_opts parse_error: argument %s requires two arguments", tok); + return 0; + } + } + + snprintf(arg, 32, "%s%s", tok, tok2); + return 1; +} + +void cvs_diff(CvsServerCtx * ctx, + const char * rep, const char * file, + const char * rev1, const char * rev2, const char * opts) +{ + char argstr[BUFSIZ], *p = argstr; + char arg[32]; + char file_buff[PATH_MAX], *basename; + + strzncpy(argstr, opts, BUFSIZ); + while (parse_patch_arg(arg, &p)) + send_string(ctx, "Argument %s\n", arg); + + send_string(ctx, "Argument -r\n"); + send_string(ctx, "Argument %s\n", rev1); + send_string(ctx, "Argument -r\n"); + send_string(ctx, "Argument %s\n", rev2); + + /* + * we need to separate the 'basename' of file in order to + * generate the Directory directive(s) + */ + strzncpy(file_buff, file, PATH_MAX); + if ((basename = strrchr(file_buff, '/'))) + { + *basename = 0; + send_string(ctx, "Directory %s/%s\n", rep, file_buff); + send_string(ctx, "%s/%s/%s\n", ctx->root, rep, file_buff); + } + else + { + send_string(ctx, "Directory %s\n", rep, file_buff); + send_string(ctx, "%s/%s\n", ctx->root, rep); + } + + send_string(ctx, "Directory .\n"); + send_string(ctx, "%s\n", ctx->root); + send_string(ctx, "Argument %s/%s\n", rep, file); + send_string(ctx, "diff\n"); + + ctx_to_fp(ctx, stdout); +} + +/* + * FIXME: the design of this sucks. It was originally designed to fork a subprocess + * which read the cvs response and send it back through a pipe the main process, + * which fdopen(3)ed the other end, and juts used regular fgets. This however + * didn't work because the reads of compressed data in the child process altered + * the compression state, and there was no way to resynchronize that state with + * the parent process. We could use threads... + */ +FILE * cvs_rlog_open(CvsServerCtx * ctx, const char * rep, const char * date_str) +{ + /* note: use of the date_str is handled in a non-standard, cvsps specific way */ + if (date_str && date_str[0]) + { + send_string(ctx, "Argument -d\n", rep); + send_string(ctx, "Argument %s<1 Jan 2038 05:00:00 -0000\n", date_str); + send_string(ctx, "Argument -d\n", rep); + send_string(ctx, "Argument %s\n", date_str); + } + + send_string(ctx, "Argument %s\n", rep); + send_string(ctx, "rlog\n"); + + /* + * FIXME: is it possible to create a 'fake' FILE * whose 'refill' + * function is below? + */ + return (FILE*)ctx; +} + +char * cvs_rlog_fgets(char * buff, int buflen, CvsServerCtx * ctx) +{ + char lbuff[BUFSIZ]; + int len; + + len = read_line(ctx, lbuff); + debug(DEBUG_TCP, "cvs_direct: rlog: read %s", lbuff); + + if (memcmp(lbuff, "M ", 2) == 0) + { + memcpy(buff, lbuff + 2, len - 2); + buff[len - 2 ] = '\n'; + buff[len - 1 ] = 0; + } + else if (memcmp(lbuff, "E ", 2) == 0) + { + debug(DEBUG_APPMSG1, "%s", lbuff + 2); + } + else if (strcmp(lbuff, "ok") == 0 ||strcmp(lbuff, "error") == 0) + { + debug(DEBUG_TCP, "cvs_direct: rlog: got command completion"); + return NULL; + } + + return buff; +} + +void cvs_rlog_close(CvsServerCtx * ctx) +{ +} + +void cvs_version(CvsServerCtx * ctx, char * client_version, char * server_version) +{ + char lbuff[BUFSIZ]; + strcpy(client_version, "Client: Concurrent Versions System (CVS) 99.99.99 (client/server) cvs-direct"); + send_string(ctx, "version\n"); + read_line(ctx, lbuff); + if (memcmp(lbuff, "M ", 2) == 0) + sprintf(server_version, "Server: %s", lbuff + 2); + else + debug(DEBUG_APPERROR, "cvs_direct: didn't read version: %s", lbuff); + + read_line(ctx, lbuff); + if (strcmp(lbuff, "ok") != 0) + debug(DEBUG_APPERROR, "cvs_direct: protocol error reading version"); + + debug(DEBUG_TCP, "cvs_direct: client version %s", client_version); + debug(DEBUG_TCP, "cvs_direct: server version %s", server_version); +} diff --git a/cvs_direct.h b/cvs_direct.h new file mode 100644 index 0000000..52a81a3 --- /dev/null +++ b/cvs_direct.h @@ -0,0 +1,24 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#ifndef CVS_DIRECT_H +#define CVS_DIRECT_H + +#ifndef HAVE_CVSSERVERCTX_DEF +#define HAVE_CVSSERVERCTX_DEF +typedef struct _CvsServerCtx CvsServerCtx; +#endif + +CvsServerCtx * open_cvs_server(char * root, int); +void close_cvs_server(CvsServerCtx*); +void cvs_rdiff(CvsServerCtx *, const char *, const char *, const char *, const char *); +void cvs_rupdate(CvsServerCtx *, const char *, const char *, const char *, int, const char *); +void cvs_diff(CvsServerCtx *, const char *, const char *, const char *, const char *, const char *); +FILE * cvs_rlog_open(CvsServerCtx *, const char *, const char *); +char * cvs_rlog_fgets(char *, int, CvsServerCtx *); +void cvs_rlog_close(CvsServerCtx *); +void cvs_version(CvsServerCtx *, char *, char *); + +#endif /* CVS_DIRECT_H */ diff --git a/cvsps.1 b/cvsps.1 new file mode 100644 index 0000000..cea0faf --- /dev/null +++ b/cvsps.1 @@ -0,0 +1,205 @@ +.TH "cvsps" 1 +.SH NAME +CVSps \- create patchset information from CVS +.SH SYNOPSIS +.B cvsps +[\-h] [\-x] [\-u] [\-z ] [\-g] [\-s ] [\-a ] [\-f ] [\-d [\-d ]] [\-l ] [\-b ] [\-r [\-r ]] [\-p ] [\-v] [\-t] [\-\-norc] [\-\-summary\-first] [\-\-test\-log ] [\-\-bkcvs] [\-\-no\-rlog] [\-\-diff\-opts