├── sblist_delete.c ├── Makefile ├── stringlist.h ├── README.md ├── sblist.c ├── sblist.h ├── svn2git.sh ├── ChangeLog ├── sys └── tree.h └── svnup.c /sblist_delete.c: -------------------------------------------------------------------------------- 1 | #include "sblist.h" 2 | #include 3 | 4 | void sblist_delete(sblist* l, size_t item) { 5 | if (l->count && item < l->count) { 6 | memmove(sblist_item_from_index(l, item), sblist_item_from_index(l, item + 1), (sblist_getsize(l) - (item + 1)) * l->itemsize); 7 | l->count--; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROG= svn 2 | OBJS= svnup.o sblist.o sblist_delete.o 3 | 4 | LDADD= -lssl -lcrypto 5 | 6 | PREFIX=/usr/local 7 | 8 | -include config.mak 9 | 10 | all: $(PROG) 11 | 12 | svnup.o: CPPFLAGS += -I. 13 | 14 | $(PROG): $(OBJS) 15 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LDADD) 16 | 17 | clean: 18 | rm -f $(PROG) $(OBJS) 19 | 20 | install: $(PROG) svn2git.sh 21 | install -Dm 755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG) 22 | install -Dm 755 svn2git.sh $(DESTDIR)$(PREFIX)/bin/svn2git 23 | 24 | .PHONY: all clean install 25 | -------------------------------------------------------------------------------- /stringlist.h: -------------------------------------------------------------------------------- 1 | #ifndef STRINGLIST_H 2 | #define STRINGLIST_H 3 | 4 | #include "sblist.h" 5 | #define _GNU_SOURCE 6 | #include 7 | 8 | #define stringlist sblist 9 | #define stringlist_new(N) sblist_new(sizeof(char*), N) 10 | #define stringlist_free(SL) sblist_free(SL) 11 | #define stringlist_getsize(SL) sblist_getsize(SL) 12 | static inline char* stringlist_get(stringlist *sl, size_t idx) { 13 | char** x = sblist_get(sl, idx); 14 | return x?*x:0; 15 | } 16 | #define stringlist_iter(SL, IVAR) for(IVAR=0;IVAR 5 | #include 6 | #include 7 | #define MY_PAGE_SIZE 4096 8 | 9 | sblist* sblist_new(size_t itemsize, size_t blockitems) { 10 | sblist* ret = (sblist*) malloc(sizeof(sblist)); 11 | sblist_init(ret, itemsize, blockitems); 12 | return ret; 13 | } 14 | 15 | static void sblist_clear(sblist* l) { 16 | l->items = NULL; 17 | l->capa = 0; 18 | l->count = 0; 19 | } 20 | 21 | void sblist_init(sblist* l, size_t itemsize, size_t blockitems) { 22 | if(l) { 23 | l->blockitems = blockitems ? blockitems : MY_PAGE_SIZE / itemsize; 24 | l->itemsize = itemsize; 25 | sblist_clear(l); 26 | } 27 | } 28 | 29 | void sblist_free_items(sblist* l) { 30 | if(l) { 31 | if(l->items) free(l->items); 32 | sblist_clear(l); 33 | } 34 | } 35 | 36 | void sblist_free(sblist* l) { 37 | if(l) { 38 | sblist_free_items(l); 39 | free(l); 40 | } 41 | } 42 | 43 | char* sblist_item_from_index(sblist* l, size_t idx) { 44 | return l->items + (idx * l->itemsize); 45 | } 46 | 47 | void* sblist_get(sblist* l, size_t item) { 48 | if(item < l->count) return (void*) sblist_item_from_index(l, item); 49 | return NULL; 50 | } 51 | 52 | int sblist_set(sblist* l, void* item, size_t pos) { 53 | if(pos >= l->count) return 0; 54 | memcpy(sblist_item_from_index(l, pos), item, l->itemsize); 55 | return 1; 56 | } 57 | 58 | int sblist_grow_if_needed(sblist* l) { 59 | char* temp; 60 | if(l->count == l->capa) { 61 | temp = realloc(l->items, (l->capa + l->blockitems) * l->itemsize); 62 | if(!temp) return 0; 63 | l->capa += l->blockitems; 64 | l->items = temp; 65 | } 66 | return 1; 67 | } 68 | 69 | int sblist_add(sblist* l, void* item) { 70 | if(!sblist_grow_if_needed(l)) return 0; 71 | l->count++; 72 | return sblist_set(l, item, l->count - 1); 73 | } 74 | -------------------------------------------------------------------------------- /sblist.h: -------------------------------------------------------------------------------- 1 | #ifndef SBLIST_H 2 | #define SBLIST_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | /* 10 | * simple buffer list. 11 | * 12 | * this thing here is basically a generic dynamic array 13 | * will realloc after every blockitems inserts 14 | * can store items of any size. 15 | * 16 | * so think of it as a by-value list, as opposed to a typical by-ref list. 17 | * you typically use it by having some struct on the stack, and pass a pointer 18 | * to sblist_add, which will copy the contents into its internal memory. 19 | * 20 | */ 21 | 22 | typedef struct { 23 | size_t itemsize; 24 | size_t blockitems; 25 | size_t count; 26 | size_t capa; 27 | char* items; 28 | } sblist; 29 | 30 | #define sblist_getsize(X) ((X)->count) 31 | #define sblist_get_count(X) ((X)->count) 32 | #define sblist_empty(X) ((X)->count == 0) 33 | 34 | /* --- for dynamic style --- */ 35 | 36 | /* allocate and initialize a new sblist */ 37 | sblist* sblist_new(size_t itemsize, size_t blockitems); 38 | 39 | /* free dynamically allocated list and its internal buffers */ 40 | void sblist_free(sblist* l); 41 | 42 | /* --- for static style --- */ 43 | /* initialize existing sblist in user-allocated storage (e.g. stack-allocated)*/ 44 | void sblist_init(sblist* l, size_t itemsize, size_t blockitems); 45 | /* free internal buffers of the list */ 46 | void sblist_free_items(sblist* l); 47 | 48 | /* in case your list contains pointers, not values, this will 49 | iterate over all list entries and free them */ 50 | void sblist_free_values(sblist *l); 51 | 52 | /* accessors */ 53 | void* sblist_get(sblist* l, size_t item); 54 | 55 | /* returns 1 on success, 0 on OOM */ 56 | int sblist_add(sblist* l, void* item); 57 | int sblist_set(sblist* l, void* item, size_t pos); 58 | void sblist_delete(sblist* l, size_t item); 59 | char* sblist_item_from_index(sblist* l, size_t idx); 60 | int sblist_grow_if_needed(sblist* l); 61 | int sblist_insert(sblist* l, void* item, size_t pos); 62 | 63 | /* you can use sblist as a stack by using sblist_add()/sblist_pop() */ 64 | /* return last item in list and remove it. returns NULL if no items in list. */ 65 | void* sblist_pop(sblist *l); 66 | 67 | /* same as sblist_add, but returns list index of new item, or -1 */ 68 | size_t sblist_addi(sblist* l, void* item); 69 | void sblist_sort(sblist *l, int (*compar)(const void *, const void *)); 70 | /* insert element into presorted list, returns listindex of new entry or -1*/ 71 | size_t sblist_insert_sorted(sblist* l, void* o, int (*compar)(const void *, const void *)); 72 | 73 | #ifndef __COUNTER__ 74 | #define __COUNTER__ __LINE__ 75 | #endif 76 | 77 | #define __sblist_concat_impl( x, y ) x##y 78 | #define __sblist_macro_concat( x, y ) __sblist_concat_impl( x, y ) 79 | #define __sblist_iterator_name __sblist_macro_concat(sblist_iterator, __COUNTER__) 80 | 81 | /* use with custom iterator variable */ 82 | #define sblist_iter_counter(LIST, ITER, PTR) \ 83 | for(size_t ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < sblist_getsize(LIST); ITER++) 84 | 85 | /* use with custom iterator variable, which is predeclared */ 86 | #define sblist_iter_counter2(LIST, ITER, PTR) \ 87 | for(ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < sblist_getsize(LIST); ITER++) 88 | 89 | /* use with custom iterator variable, which is predeclared and signed */ 90 | /* useful for a loop which can delete items from the list, and then decrease the iterator var. */ 91 | #define sblist_iter_counter2s(LIST, ITER, PTR) \ 92 | for(ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < (ssize_t) sblist_getsize(LIST); ITER++) 93 | 94 | 95 | /* uses "magic" iterator variable */ 96 | #define sblist_iter(LIST, PTR) sblist_iter_counter(LIST, __sblist_iterator_name, PTR) 97 | 98 | #ifdef __cplusplus 99 | } 100 | #endif 101 | 102 | #pragma RcB2 DEP "sblist*.c" 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /svn2git.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if test -z "$SVN" ; then 4 | if type svn >/dev/null 2>&1 ; then 5 | SVN=svn 6 | else 7 | SVN="$(dirname $(readlink -f "$0"))"/svn 8 | fi 9 | else 10 | # in case SVN is set to a relative path, resolve it, for chdir reasons. 11 | rp="$(realpath "$SVN" 2>/dev/null)" 12 | test $? = 0 && SVN="$rp" 13 | fi 14 | 15 | usage() { 16 | echo "$0 COMMAND [OPTIONS...]" 17 | echo "COMMANDs:" 18 | echo 19 | echo "convert URL DIR" 20 | echo "converts the svn repo in URL to a git repo in DIR" 21 | echo "set env var endrev to a revision number if you dont" 22 | echo "want to mirror till the last commit" 23 | echo 24 | echo "update DIR" 25 | echo "updates svn repo clone in DIR to latest commit" 26 | echo "endrev end var can be used too" 27 | echo 28 | echo "example:" 29 | echo "$0 convert svn://svn.code.sf.net/p/dosbox/code-0/dosbox/trunk dosbox" 30 | echo 31 | echo "if you want to use a specific SVN program, set SVN env var to it." 32 | exit 1 33 | } 34 | get_ini() { printf "%s/.git/svn2git.ini\n" "$1" ; } 35 | set_ini_val() { sed -e 's@^'$2'=.*$@'$2'='"$3"'@' -i "$(get_ini "$1")" ; } 36 | set_rev() { set_ini_val "$1" rev $2 ; } 37 | set_url() { set_ini_val "$1" url "$2" ; } 38 | get_ini_val() { 39 | test -e "$(get_ini "$1")" || { printf "-1\n" ; return 1 ; } 40 | grep '^'$2'=' < "$(get_ini "$1")" | cut -d = -f 2 41 | } 42 | get_rev() { get_ini_val "$1" rev ; } 43 | get_url() { get_ini_val "$1" url ; } 44 | 45 | if test "$1" = convert ; then 46 | test -z "$3" && usage 47 | 48 | repo="$2" 49 | outdir="$3" 50 | rev=1 51 | need_init=true 52 | 53 | elif test "$1" = update ; then 54 | test -z "$2" && usage 55 | outdir="$2" 56 | rev=$(get_rev "$outdir") 57 | test $rev = -1 && { echo "cant find $(get_ini "$outdir")">&2 ; exit 1; } 58 | repo=$(get_url "$outdir") 59 | rev=$((rev + 1)) 60 | need_init=false 61 | else 62 | 63 | usage 64 | fi 65 | 66 | tmp1=/tmp/svnup2git.1.$$ 67 | tmp2=/tmp/svnup2git.2.$$ 68 | trap "rm -f $tmp1 $tmp2 2>/dev/null" EXIT TERM INT 69 | 70 | 71 | test -z "$endrev" && { 72 | $SVN info "$repo" > $tmp1 73 | ret=$? 74 | test $ret = 0 || { echo "error: couldn't fetch current rev from $repo">&2 ; exit 1; } 75 | endrev=$(grep '^Revision:' < $tmp1 | cut -d ' ' -f 2) 76 | } 77 | 78 | if $need_init ; then 79 | test -d "$outdir" && { echo "error: $outdir already existing! did you mean to use 'update'?" ; exit 1 ; } 80 | mkdir -p "$outdir" 81 | fi 82 | cd "$outdir" 83 | 84 | if $need_init ; then 85 | git init 86 | cat << EOF > $(get_ini .) 87 | rev=-1 88 | url=$repo 89 | EOF 90 | gbranch=master 91 | test "$(basename "$repo" | sed 's,/,,g')" = trunk && gbranch=trunk 92 | git branch -m $gbranch 93 | fi 94 | 95 | while test $rev -lt $((endrev + 1)) ; do 96 | 97 | $SVN co -r $rev "$repo" . 2> $tmp1 98 | ret=$? 99 | if test $ret != 0 ; then 100 | path_issue=false 101 | grep E195012 < $tmp1 >/dev/null && path_issue=true 102 | grep 'svn: 160013' < $tmp1 >/dev/null && path_issue=true 103 | grep 'Target path.*does not exist' < $tmp1 >/dev/null && path_issue=true 104 | if $path_issue ; then 105 | echo "skipping rev $rev as path does not exit (yet?)" 106 | rev=$((rev + 1)) 107 | continue 108 | else 109 | cat $tmp1 110 | exit 1 111 | fi 112 | fi 113 | 114 | git add --all . # . adds *all* files, even dotfiles and dirs not listed with *, same as '*' 115 | 116 | # unstage our own files from repo 117 | # we can't check in .gitignore, since that might at some point be added in a commit 118 | for x in $(git status --porcelain | awk '/ \.svn\// || / \.svnup\// {for(i=1;i<=NF;++i) if($i ~ /\.svn\// || $i ~/\.svnup\//) {print($i); break;}}') ; do 119 | git rm --cached "$x" >/dev/null 120 | ret=$? 121 | if test $ret != 0 ; then 122 | echo "got error trying to git rm $x" 123 | exit 1 124 | fi 125 | done 126 | 127 | $SVN log -r "$rev" . > "$tmp1" 128 | loglines=$(wc -l $tmp1 | cut -d " " -f 1) 129 | test $loglines = 1 && { 130 | echo "warning: empty revision $rev">&2 131 | rev=$((rev + 1)) 132 | continue 133 | } 134 | tail -n $((loglines - 1)) < $tmp1 > $tmp2 135 | head -n $((loglines - 2)) < $tmp2 > $tmp1 136 | author=$(head -n 1 < $tmp1 | cut -d '|' -f 2 | sed -e 's/^ //' -e 's/ $//') 137 | author_sane=$(head -n 1 < $tmp1 | cut -d '|' -f 2 | sed -e 's/^ //' -e 's/ $//' -e 's/ /./g') 138 | date=$(head -n 1 < $tmp1 | cut -d '|' -f 3 | sed -e 's/^ //' -e 's/ $//') 139 | printf "r%u|" $rev > $tmp2 140 | tail -n $((loglines - 4)) < $tmp1 >> $tmp2 141 | 142 | git commit --author="$author <$author_sane@localhost>" --date="$date" -F $tmp2 --allow-empty 143 | set_rev . $rev 144 | rev=$((rev + 1)) 145 | done 146 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | svnup-1.08 - 12 October 2019 2 | * Added a file named .svnversion containing the revision number. 3 | * Added code to remove empty directories when all files that were there 4 | have been deleted. 5 | 6 | svnup-1.07 - 22 November 2014 7 | * Fixed a bug where symbolic links in the repository were being 8 | processed every time when the -t option was used (Bug 193837). 9 | * Fixed a bug that was causing periodic freezing 10 | - special thanks to Rudolf Cejka . 11 | * Fixed a regression where specifying the revision number no longer 12 | worked. 13 | * Fixed SSL connection errors. 14 | * Minor code cleanup. 15 | 16 | svnup-1.06 - 5 September 2014 17 | * Added support for repositories that are published in non-root 18 | directories (Bug 191132). 19 | * Fixed a bug where chunked-transfer offsets occurring inside of an 20 | svn tag resulted in a segfault. 21 | * Cleaned up check_command_success. 22 | 23 | svnup-1.05 - 25 April 2014 24 | * Fixed a bug to properly handle the case when files in the repository 25 | are replaced with directories of the same name. 26 | 27 | svnup-1.04 - 22 April 2014 28 | * Fixed a issue where symbolic links found in the source trees were 29 | being updated during each run. 30 | 31 | svnup-1.03 - 15 February 2014 32 | * Fixed a problem with https transfers when using FreeBSD 10.0. 33 | * Changed all instances of lstat(2) to stat(2) to avoid problems when 34 | filesystem links are used. 35 | 36 | svnup-1.02 - 3 January 2014 37 | * Fixed bugs with freeing nodes in the red-black trees and 38 | additional code cleanup. 39 | - special thanks to Sitsofe Wheeler . 40 | 41 | svnup-1.01 - 12 December 2013 42 | * Fixed a bug when attempting to access an invalid repository path 43 | using the http/https protocol. 44 | 45 | svnup-1.0 - 3 August 2013 46 | * Fixed a bug with using custom port numbers. 47 | 48 | svnup-0.99 - 24 July 2013 49 | * Bugfix 50 | 51 | svnup-0.98 - 22 July 2013 52 | * Fixed a bug related to recent (v 1.8) updates to the svn protocol 53 | (pr ports/180485). 54 | * Fixed a bug where version tags were inserted into files that did not 55 | have the FreeBSD=%H keyword associated with them (ports/180490). 56 | 57 | svnup-0.97 - 30 June 2013 58 | * Fixed bugs in find_local_files and set_configuration_parameters 59 | - special thanks to Rudolf Cejka . 60 | 61 | svnup-0.96 - 19 June 2013 62 | * Added a command line option for overriding the location where 63 | the lists of known files are stored. 64 | * Simplified the procedure that removes (prunes) local files. 65 | 66 | svnup-0.95 - 15 June 2013 67 | * Fixed a bug with the $FreeBSD$ line (pr ports/179548) 68 | * Fixed a bug where local files were not being removed when using 69 | the -t option. 70 | 71 | svnup-0.9 - 8 June 2013 72 | * Added a new command line and show all files that exist in the 73 | target directory structure that do not exist in the repository. 74 | * Fixed bug with the way the progress indicator is displayed. 75 | * Fixed segmentation fault that occurred when using the svn protocol 76 | and displaying the progress indicator. 77 | 78 | svnup-0.73 - 2 June 2013 79 | * Significantly improved performance when using the http/https protocol 80 | to fetch incremental updates. 81 | * Added a new command line and section option to allow a custom port 82 | number to connect to. 83 | * Added a new command line option to override the specified section's 84 | protocol. 85 | * Added a new command line and section option to remove (trim) any files 86 | found in the local copy that are not found in the repository. 87 | * Fixed a display bug in the progress indicator. 88 | 89 | svnup-0.72 - 16 May 2013 90 | * Fixed a bug with the -r command line option. 91 | * Added a progress indicator to the output when the verbosity is > 1. 92 | * More code cleanup - special thanks to Eitan Adler . 93 | * Switched from non-blocking to blocking I/O. 94 | 95 | svnup-0.71 - 9 May 2013 96 | * Fixed a bug with the -V command line option. 97 | * Code cleanup - special thanks to Eitan Adler . 98 | 99 | svnup-0.70 - 7 May 2013 100 | * Merged the http and svn code paths. 101 | * Replaced binary search tree code with red-black tree defined 102 | in 103 | * Fixed a bug where revision tags were improperly included if the 104 | FreeBSD=%H was not included in the file attributes. 105 | * Changed the default directory for storing known files to 106 | /var/db/svnup/. 107 | * Added the command line option -n to display section's the last 108 | known revision and exit. 109 | 110 | svnup-0.63 - 17 April 2013 111 | * Fixed a bug where filenames containing multiple hex encoded 112 | characters weren't being properly decoded. 113 | 114 | svnup-0.62 - 11 April 2013 115 | * Implemented binary search tree for faster lookups of "known files". 116 | * Added support for handling symbolic links in the repository. 117 | * Fixed a bug where file names with hex encoded characters sent 118 | during http transfers weren't properly decoded. 119 | * Fixed bug when allocating space for the /tmp/svnup directory - 120 | special thanks to (Ilya A. Arkhipov ). 121 | * Fixed bug where incorrect number of bytes were sent in send_command - 122 | special thanks to . 123 | 124 | svnup-0.61 - 6 April 2013 125 | * Fixed a bug with file deletion. 126 | * Minor speed improvement with "known file" lookup. 127 | * Fixed several valgrind-detected small memory leaks. 128 | * Fixed a segfault when https:// was added to a -h parameter. 129 | 130 | svnup-0.60 - 3 April 2013 131 | * http/https support added. 132 | * added support for a configuration/preferences/aliases file. 133 | * added IPv6 support. 134 | 135 | svnup-0.56 - 2 March 2013 136 | * Fixed bug in check_md5 when reinserting revision tags in known files - 137 | special thanks to Rudolf Cejka . 138 | 139 | svnup-0.55 - 27 February 2013 140 | * Major rewrite of command transmission routine (what were once 141 | individual commands are now grouped together and sent in bulk). 142 | Significant performance improvement and the tool is now useable. 143 | * svnup is now in the FreeBSD ports tree under net/svnup. 144 | 145 | svnup-0.51 - 24 February 2013 146 | * Performance improvement (it's now just painfully slow). 147 | 148 | svnup-0.5 - 23 February 2013 149 | * Initial proof-of-concept release. Excruciatingly slow performance. 150 | -------------------------------------------------------------------------------- /sys/tree.h: -------------------------------------------------------------------------------- 1 | /* $NetBSD: tree.h,v 1.20 2013/09/14 13:20:45 joerg Exp $ */ 2 | /* $OpenBSD: tree.h,v 1.13 2011/07/09 00:19:45 pirofti Exp $ */ 3 | /* 4 | * Copyright 2002 Niels Provos 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #ifndef _SYS_TREE_H_ 29 | #define _SYS_TREE_H_ 30 | 31 | /* 32 | * This file defines data structures for different types of trees: 33 | * splay trees and red-black trees. 34 | * 35 | * A splay tree is a self-organizing data structure. Every operation 36 | * on the tree causes a splay to happen. The splay moves the requested 37 | * node to the root of the tree and partly rebalances it. 38 | * 39 | * This has the benefit that request locality causes faster lookups as 40 | * the requested nodes move to the top of the tree. On the other hand, 41 | * every lookup causes memory writes. 42 | * 43 | * The Balance Theorem bounds the total access time for m operations 44 | * and n inserts on an initially empty tree as O((m + n)lg n). The 45 | * amortized cost for a sequence of m accesses to a splay tree is O(lg n); 46 | * 47 | * A red-black tree is a binary search tree with the node color as an 48 | * extra attribute. It fulfills a set of conditions: 49 | * - every search path from the root to a leaf consists of the 50 | * same number of black nodes, 51 | * - each red node (except for the root) has a black parent, 52 | * - each leaf node is black. 53 | * 54 | * Every operation on a red-black tree is bounded as O(lg n). 55 | * The maximum height of a red-black tree is 2lg (n+1). 56 | */ 57 | 58 | #define SPLAY_HEAD(name, type) \ 59 | struct name { \ 60 | struct type *sph_root; /* root of the tree */ \ 61 | } 62 | 63 | #define SPLAY_INITIALIZER(root) \ 64 | { NULL } 65 | 66 | #define SPLAY_INIT(root) do { \ 67 | (root)->sph_root = NULL; \ 68 | } while (/*CONSTCOND*/ 0) 69 | 70 | #define SPLAY_ENTRY(type) \ 71 | struct { \ 72 | struct type *spe_left; /* left element */ \ 73 | struct type *spe_right; /* right element */ \ 74 | } 75 | 76 | #define SPLAY_LEFT(elm, field) (elm)->field.spe_left 77 | #define SPLAY_RIGHT(elm, field) (elm)->field.spe_right 78 | #define SPLAY_ROOT(head) (head)->sph_root 79 | #define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) 80 | 81 | /* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ 82 | #define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ 83 | SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ 84 | SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ 85 | (head)->sph_root = tmp; \ 86 | } while (/*CONSTCOND*/ 0) 87 | 88 | #define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ 89 | SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ 90 | SPLAY_LEFT(tmp, field) = (head)->sph_root; \ 91 | (head)->sph_root = tmp; \ 92 | } while (/*CONSTCOND*/ 0) 93 | 94 | #define SPLAY_LINKLEFT(head, tmp, field) do { \ 95 | SPLAY_LEFT(tmp, field) = (head)->sph_root; \ 96 | tmp = (head)->sph_root; \ 97 | (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ 98 | } while (/*CONSTCOND*/ 0) 99 | 100 | #define SPLAY_LINKRIGHT(head, tmp, field) do { \ 101 | SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ 102 | tmp = (head)->sph_root; \ 103 | (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ 104 | } while (/*CONSTCOND*/ 0) 105 | 106 | #define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ 107 | SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ 108 | SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ 109 | SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ 110 | SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ 111 | } while (/*CONSTCOND*/ 0) 112 | 113 | /* Generates prototypes and inline functions */ 114 | 115 | #define SPLAY_PROTOTYPE(name, type, field, cmp) \ 116 | void name##_SPLAY(struct name *, struct type *); \ 117 | void name##_SPLAY_MINMAX(struct name *, int); \ 118 | struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ 119 | struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ 120 | \ 121 | /* Finds the node with the same key as elm */ \ 122 | static __inline struct type * \ 123 | name##_SPLAY_FIND(struct name *head, struct type *elm) \ 124 | { \ 125 | if (SPLAY_EMPTY(head)) \ 126 | return(NULL); \ 127 | name##_SPLAY(head, elm); \ 128 | if ((cmp)(elm, (head)->sph_root) == 0) \ 129 | return (head->sph_root); \ 130 | return (NULL); \ 131 | } \ 132 | \ 133 | static __inline __unused struct type * \ 134 | name##_SPLAY_NEXT(struct name *head, struct type *elm) \ 135 | { \ 136 | name##_SPLAY(head, elm); \ 137 | if (SPLAY_RIGHT(elm, field) != NULL) { \ 138 | elm = SPLAY_RIGHT(elm, field); \ 139 | while (SPLAY_LEFT(elm, field) != NULL) { \ 140 | elm = SPLAY_LEFT(elm, field); \ 141 | } \ 142 | } else \ 143 | elm = NULL; \ 144 | return (elm); \ 145 | } \ 146 | \ 147 | static __unused __inline struct type * \ 148 | name##_SPLAY_MIN_MAX(struct name *head, int val) \ 149 | { \ 150 | name##_SPLAY_MINMAX(head, val); \ 151 | return (SPLAY_ROOT(head)); \ 152 | } 153 | 154 | /* Main splay operation. 155 | * Moves node close to the key of elm to top 156 | */ 157 | #define SPLAY_GENERATE(name, type, field, cmp) \ 158 | struct type * \ 159 | name##_SPLAY_INSERT(struct name *head, struct type *elm) \ 160 | { \ 161 | if (SPLAY_EMPTY(head)) { \ 162 | SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ 163 | } else { \ 164 | int __comp; \ 165 | name##_SPLAY(head, elm); \ 166 | __comp = (cmp)(elm, (head)->sph_root); \ 167 | if(__comp < 0) { \ 168 | SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ 169 | SPLAY_RIGHT(elm, field) = (head)->sph_root; \ 170 | SPLAY_LEFT((head)->sph_root, field) = NULL; \ 171 | } else if (__comp > 0) { \ 172 | SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ 173 | SPLAY_LEFT(elm, field) = (head)->sph_root; \ 174 | SPLAY_RIGHT((head)->sph_root, field) = NULL; \ 175 | } else \ 176 | return ((head)->sph_root); \ 177 | } \ 178 | (head)->sph_root = (elm); \ 179 | return (NULL); \ 180 | } \ 181 | \ 182 | struct type * \ 183 | name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ 184 | { \ 185 | struct type *__tmp; \ 186 | if (SPLAY_EMPTY(head)) \ 187 | return (NULL); \ 188 | name##_SPLAY(head, elm); \ 189 | if ((cmp)(elm, (head)->sph_root) == 0) { \ 190 | if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ 191 | (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ 192 | } else { \ 193 | __tmp = SPLAY_RIGHT((head)->sph_root, field); \ 194 | (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ 195 | name##_SPLAY(head, elm); \ 196 | SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ 197 | } \ 198 | return (elm); \ 199 | } \ 200 | return (NULL); \ 201 | } \ 202 | \ 203 | void \ 204 | name##_SPLAY(struct name *head, struct type *elm) \ 205 | { \ 206 | struct type __node, *__left, *__right, *__tmp; \ 207 | int __comp; \ 208 | \ 209 | SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ 210 | __left = __right = &__node; \ 211 | \ 212 | while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \ 213 | if (__comp < 0) { \ 214 | __tmp = SPLAY_LEFT((head)->sph_root, field); \ 215 | if (__tmp == NULL) \ 216 | break; \ 217 | if ((cmp)(elm, __tmp) < 0){ \ 218 | SPLAY_ROTATE_RIGHT(head, __tmp, field); \ 219 | if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ 220 | break; \ 221 | } \ 222 | SPLAY_LINKLEFT(head, __right, field); \ 223 | } else if (__comp > 0) { \ 224 | __tmp = SPLAY_RIGHT((head)->sph_root, field); \ 225 | if (__tmp == NULL) \ 226 | break; \ 227 | if ((cmp)(elm, __tmp) > 0){ \ 228 | SPLAY_ROTATE_LEFT(head, __tmp, field); \ 229 | if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ 230 | break; \ 231 | } \ 232 | SPLAY_LINKRIGHT(head, __left, field); \ 233 | } \ 234 | } \ 235 | SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ 236 | } \ 237 | \ 238 | /* Splay with either the minimum or the maximum element \ 239 | * Used to find minimum or maximum element in tree. \ 240 | */ \ 241 | void name##_SPLAY_MINMAX(struct name *head, int __comp) \ 242 | { \ 243 | struct type __node, *__left, *__right, *__tmp; \ 244 | \ 245 | SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ 246 | __left = __right = &__node; \ 247 | \ 248 | while (1) { \ 249 | if (__comp < 0) { \ 250 | __tmp = SPLAY_LEFT((head)->sph_root, field); \ 251 | if (__tmp == NULL) \ 252 | break; \ 253 | if (__comp < 0){ \ 254 | SPLAY_ROTATE_RIGHT(head, __tmp, field); \ 255 | if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ 256 | break; \ 257 | } \ 258 | SPLAY_LINKLEFT(head, __right, field); \ 259 | } else if (__comp > 0) { \ 260 | __tmp = SPLAY_RIGHT((head)->sph_root, field); \ 261 | if (__tmp == NULL) \ 262 | break; \ 263 | if (__comp > 0) { \ 264 | SPLAY_ROTATE_LEFT(head, __tmp, field); \ 265 | if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ 266 | break; \ 267 | } \ 268 | SPLAY_LINKRIGHT(head, __left, field); \ 269 | } \ 270 | } \ 271 | SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ 272 | } 273 | 274 | #define SPLAY_NEGINF -1 275 | #define SPLAY_INF 1 276 | 277 | #define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) 278 | #define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) 279 | #define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) 280 | #define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) 281 | #define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ 282 | : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) 283 | #define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ 284 | : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) 285 | 286 | #define SPLAY_FOREACH(x, name, head) \ 287 | for ((x) = SPLAY_MIN(name, head); \ 288 | (x) != NULL; \ 289 | (x) = SPLAY_NEXT(name, head, x)) 290 | 291 | /* Macros that define a red-black tree */ 292 | #define RB_HEAD(name, type) \ 293 | struct name { \ 294 | struct type *rbh_root; /* root of the tree */ \ 295 | } 296 | 297 | #define RB_INITIALIZER(root) \ 298 | { NULL } 299 | 300 | #define RB_INIT(root) do { \ 301 | (root)->rbh_root = NULL; \ 302 | } while (/*CONSTCOND*/ 0) 303 | 304 | #define RB_BLACK 0 305 | #define RB_RED 1 306 | #define RB_ENTRY(type) \ 307 | struct { \ 308 | struct type *rbe_left; /* left element */ \ 309 | struct type *rbe_right; /* right element */ \ 310 | struct type *rbe_parent; /* parent element */ \ 311 | int rbe_color; /* node color */ \ 312 | } 313 | 314 | #define RB_LEFT(elm, field) (elm)->field.rbe_left 315 | #define RB_RIGHT(elm, field) (elm)->field.rbe_right 316 | #define RB_PARENT(elm, field) (elm)->field.rbe_parent 317 | #define RB_COLOR(elm, field) (elm)->field.rbe_color 318 | #define RB_ROOT(head) (head)->rbh_root 319 | #define RB_EMPTY(head) (RB_ROOT(head) == NULL) 320 | 321 | #define RB_SET(elm, parent, field) do { \ 322 | RB_PARENT(elm, field) = parent; \ 323 | RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ 324 | RB_COLOR(elm, field) = RB_RED; \ 325 | } while (/*CONSTCOND*/ 0) 326 | 327 | #define RB_SET_BLACKRED(black, red, field) do { \ 328 | RB_COLOR(black, field) = RB_BLACK; \ 329 | RB_COLOR(red, field) = RB_RED; \ 330 | } while (/*CONSTCOND*/ 0) 331 | 332 | #ifndef RB_AUGMENT 333 | #define RB_AUGMENT(x) do {} while (/*CONSTCOND*/ 0) 334 | #endif 335 | 336 | #define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ 337 | (tmp) = RB_RIGHT(elm, field); \ 338 | if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \ 339 | RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ 340 | } \ 341 | RB_AUGMENT(elm); \ 342 | if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ 343 | if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ 344 | RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ 345 | else \ 346 | RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ 347 | } else \ 348 | (head)->rbh_root = (tmp); \ 349 | RB_LEFT(tmp, field) = (elm); \ 350 | RB_PARENT(elm, field) = (tmp); \ 351 | RB_AUGMENT(tmp); \ 352 | if ((RB_PARENT(tmp, field))) \ 353 | RB_AUGMENT(RB_PARENT(tmp, field)); \ 354 | } while (/*CONSTCOND*/ 0) 355 | 356 | #define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ 357 | (tmp) = RB_LEFT(elm, field); \ 358 | if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \ 359 | RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ 360 | } \ 361 | RB_AUGMENT(elm); \ 362 | if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ 363 | if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ 364 | RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ 365 | else \ 366 | RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ 367 | } else \ 368 | (head)->rbh_root = (tmp); \ 369 | RB_RIGHT(tmp, field) = (elm); \ 370 | RB_PARENT(elm, field) = (tmp); \ 371 | RB_AUGMENT(tmp); \ 372 | if ((RB_PARENT(tmp, field))) \ 373 | RB_AUGMENT(RB_PARENT(tmp, field)); \ 374 | } while (/*CONSTCOND*/ 0) 375 | 376 | /* Generates prototypes and inline functions */ 377 | #define RB_PROTOTYPE(name, type, field, cmp) \ 378 | RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) 379 | #define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ 380 | RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static) 381 | #define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ 382 | attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \ 383 | attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\ 384 | attr struct type *name##_RB_REMOVE(struct name *, struct type *); \ 385 | attr struct type *name##_RB_INSERT(struct name *, struct type *); \ 386 | attr struct type *name##_RB_FIND(struct name *, struct type *); \ 387 | attr struct type *name##_RB_NFIND(struct name *, struct type *); \ 388 | attr struct type *name##_RB_NEXT(struct type *); \ 389 | attr struct type *name##_RB_PREV(struct type *); \ 390 | attr struct type *name##_RB_MINMAX(struct name *, int); \ 391 | \ 392 | 393 | /* Main rb operation. 394 | * Moves node close to the key of elm to top 395 | */ 396 | #define RB_GENERATE(name, type, field, cmp) \ 397 | RB_GENERATE_INTERNAL(name, type, field, cmp,) 398 | #define RB_GENERATE_STATIC(name, type, field, cmp) \ 399 | RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static) 400 | #define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ 401 | attr void \ 402 | name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ 403 | { \ 404 | struct type *parent, *gparent, *tmp; \ 405 | while ((parent = RB_PARENT(elm, field)) != NULL && \ 406 | RB_COLOR(parent, field) == RB_RED) { \ 407 | gparent = RB_PARENT(parent, field); \ 408 | if (parent == RB_LEFT(gparent, field)) { \ 409 | tmp = RB_RIGHT(gparent, field); \ 410 | if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ 411 | RB_COLOR(tmp, field) = RB_BLACK; \ 412 | RB_SET_BLACKRED(parent, gparent, field);\ 413 | elm = gparent; \ 414 | continue; \ 415 | } \ 416 | if (RB_RIGHT(parent, field) == elm) { \ 417 | RB_ROTATE_LEFT(head, parent, tmp, field);\ 418 | tmp = parent; \ 419 | parent = elm; \ 420 | elm = tmp; \ 421 | } \ 422 | RB_SET_BLACKRED(parent, gparent, field); \ 423 | RB_ROTATE_RIGHT(head, gparent, tmp, field); \ 424 | } else { \ 425 | tmp = RB_LEFT(gparent, field); \ 426 | if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ 427 | RB_COLOR(tmp, field) = RB_BLACK; \ 428 | RB_SET_BLACKRED(parent, gparent, field);\ 429 | elm = gparent; \ 430 | continue; \ 431 | } \ 432 | if (RB_LEFT(parent, field) == elm) { \ 433 | RB_ROTATE_RIGHT(head, parent, tmp, field);\ 434 | tmp = parent; \ 435 | parent = elm; \ 436 | elm = tmp; \ 437 | } \ 438 | RB_SET_BLACKRED(parent, gparent, field); \ 439 | RB_ROTATE_LEFT(head, gparent, tmp, field); \ 440 | } \ 441 | } \ 442 | RB_COLOR(head->rbh_root, field) = RB_BLACK; \ 443 | } \ 444 | \ 445 | attr void \ 446 | name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ 447 | { \ 448 | struct type *tmp; \ 449 | while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ 450 | elm != RB_ROOT(head)) { \ 451 | if (RB_LEFT(parent, field) == elm) { \ 452 | tmp = RB_RIGHT(parent, field); \ 453 | if (RB_COLOR(tmp, field) == RB_RED) { \ 454 | RB_SET_BLACKRED(tmp, parent, field); \ 455 | RB_ROTATE_LEFT(head, parent, tmp, field);\ 456 | tmp = RB_RIGHT(parent, field); \ 457 | } \ 458 | if ((RB_LEFT(tmp, field) == NULL || \ 459 | RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ 460 | (RB_RIGHT(tmp, field) == NULL || \ 461 | RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ 462 | RB_COLOR(tmp, field) = RB_RED; \ 463 | elm = parent; \ 464 | parent = RB_PARENT(elm, field); \ 465 | } else { \ 466 | if (RB_RIGHT(tmp, field) == NULL || \ 467 | RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ 468 | struct type *oleft; \ 469 | if ((oleft = RB_LEFT(tmp, field)) \ 470 | != NULL) \ 471 | RB_COLOR(oleft, field) = RB_BLACK;\ 472 | RB_COLOR(tmp, field) = RB_RED; \ 473 | RB_ROTATE_RIGHT(head, tmp, oleft, field);\ 474 | tmp = RB_RIGHT(parent, field); \ 475 | } \ 476 | RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ 477 | RB_COLOR(parent, field) = RB_BLACK; \ 478 | if (RB_RIGHT(tmp, field)) \ 479 | RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ 480 | RB_ROTATE_LEFT(head, parent, tmp, field);\ 481 | elm = RB_ROOT(head); \ 482 | break; \ 483 | } \ 484 | } else { \ 485 | tmp = RB_LEFT(parent, field); \ 486 | if (RB_COLOR(tmp, field) == RB_RED) { \ 487 | RB_SET_BLACKRED(tmp, parent, field); \ 488 | RB_ROTATE_RIGHT(head, parent, tmp, field);\ 489 | tmp = RB_LEFT(parent, field); \ 490 | } \ 491 | if ((RB_LEFT(tmp, field) == NULL || \ 492 | RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ 493 | (RB_RIGHT(tmp, field) == NULL || \ 494 | RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ 495 | RB_COLOR(tmp, field) = RB_RED; \ 496 | elm = parent; \ 497 | parent = RB_PARENT(elm, field); \ 498 | } else { \ 499 | if (RB_LEFT(tmp, field) == NULL || \ 500 | RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ 501 | struct type *oright; \ 502 | if ((oright = RB_RIGHT(tmp, field)) \ 503 | != NULL) \ 504 | RB_COLOR(oright, field) = RB_BLACK;\ 505 | RB_COLOR(tmp, field) = RB_RED; \ 506 | RB_ROTATE_LEFT(head, tmp, oright, field);\ 507 | tmp = RB_LEFT(parent, field); \ 508 | } \ 509 | RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ 510 | RB_COLOR(parent, field) = RB_BLACK; \ 511 | if (RB_LEFT(tmp, field)) \ 512 | RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ 513 | RB_ROTATE_RIGHT(head, parent, tmp, field);\ 514 | elm = RB_ROOT(head); \ 515 | break; \ 516 | } \ 517 | } \ 518 | } \ 519 | if (elm) \ 520 | RB_COLOR(elm, field) = RB_BLACK; \ 521 | } \ 522 | \ 523 | attr struct type * \ 524 | name##_RB_REMOVE(struct name *head, struct type *elm) \ 525 | { \ 526 | struct type *child, *parent, *old = elm; \ 527 | int color; \ 528 | if (RB_LEFT(elm, field) == NULL) \ 529 | child = RB_RIGHT(elm, field); \ 530 | else if (RB_RIGHT(elm, field) == NULL) \ 531 | child = RB_LEFT(elm, field); \ 532 | else { \ 533 | struct type *left; \ 534 | elm = RB_RIGHT(elm, field); \ 535 | while ((left = RB_LEFT(elm, field)) != NULL) \ 536 | elm = left; \ 537 | child = RB_RIGHT(elm, field); \ 538 | parent = RB_PARENT(elm, field); \ 539 | color = RB_COLOR(elm, field); \ 540 | if (child) \ 541 | RB_PARENT(child, field) = parent; \ 542 | if (parent) { \ 543 | if (RB_LEFT(parent, field) == elm) \ 544 | RB_LEFT(parent, field) = child; \ 545 | else \ 546 | RB_RIGHT(parent, field) = child; \ 547 | RB_AUGMENT(parent); \ 548 | } else \ 549 | RB_ROOT(head) = child; \ 550 | if (RB_PARENT(elm, field) == old) \ 551 | parent = elm; \ 552 | (elm)->field = (old)->field; \ 553 | if (RB_PARENT(old, field)) { \ 554 | if (RB_LEFT(RB_PARENT(old, field), field) == old)\ 555 | RB_LEFT(RB_PARENT(old, field), field) = elm;\ 556 | else \ 557 | RB_RIGHT(RB_PARENT(old, field), field) = elm;\ 558 | RB_AUGMENT(RB_PARENT(old, field)); \ 559 | } else \ 560 | RB_ROOT(head) = elm; \ 561 | RB_PARENT(RB_LEFT(old, field), field) = elm; \ 562 | if (RB_RIGHT(old, field)) \ 563 | RB_PARENT(RB_RIGHT(old, field), field) = elm; \ 564 | if (parent) { \ 565 | left = parent; \ 566 | do { \ 567 | RB_AUGMENT(left); \ 568 | } while ((left = RB_PARENT(left, field)) != NULL); \ 569 | } \ 570 | goto color; \ 571 | } \ 572 | parent = RB_PARENT(elm, field); \ 573 | color = RB_COLOR(elm, field); \ 574 | if (child) \ 575 | RB_PARENT(child, field) = parent; \ 576 | if (parent) { \ 577 | if (RB_LEFT(parent, field) == elm) \ 578 | RB_LEFT(parent, field) = child; \ 579 | else \ 580 | RB_RIGHT(parent, field) = child; \ 581 | RB_AUGMENT(parent); \ 582 | } else \ 583 | RB_ROOT(head) = child; \ 584 | color: \ 585 | if (color == RB_BLACK) \ 586 | name##_RB_REMOVE_COLOR(head, parent, child); \ 587 | return (old); \ 588 | } \ 589 | \ 590 | /* Inserts a node into the RB tree */ \ 591 | attr struct type * \ 592 | name##_RB_INSERT(struct name *head, struct type *elm) \ 593 | { \ 594 | struct type *tmp; \ 595 | struct type *parent = NULL; \ 596 | int comp = 0; \ 597 | tmp = RB_ROOT(head); \ 598 | while (tmp) { \ 599 | parent = tmp; \ 600 | comp = (cmp)(elm, parent); \ 601 | if (comp < 0) \ 602 | tmp = RB_LEFT(tmp, field); \ 603 | else if (comp > 0) \ 604 | tmp = RB_RIGHT(tmp, field); \ 605 | else \ 606 | return (tmp); \ 607 | } \ 608 | RB_SET(elm, parent, field); \ 609 | if (parent != NULL) { \ 610 | if (comp < 0) \ 611 | RB_LEFT(parent, field) = elm; \ 612 | else \ 613 | RB_RIGHT(parent, field) = elm; \ 614 | RB_AUGMENT(parent); \ 615 | } else \ 616 | RB_ROOT(head) = elm; \ 617 | name##_RB_INSERT_COLOR(head, elm); \ 618 | return (NULL); \ 619 | } \ 620 | \ 621 | /* Finds the node with the same key as elm */ \ 622 | attr struct type * \ 623 | name##_RB_FIND(struct name *head, struct type *elm) \ 624 | { \ 625 | struct type *tmp = RB_ROOT(head); \ 626 | int comp; \ 627 | while (tmp) { \ 628 | comp = cmp(elm, tmp); \ 629 | if (comp < 0) \ 630 | tmp = RB_LEFT(tmp, field); \ 631 | else if (comp > 0) \ 632 | tmp = RB_RIGHT(tmp, field); \ 633 | else \ 634 | return (tmp); \ 635 | } \ 636 | return (NULL); \ 637 | } \ 638 | \ 639 | /* Finds the first node greater than or equal to the search key */ \ 640 | attr struct type * \ 641 | name##_RB_NFIND(struct name *head, struct type *elm) \ 642 | { \ 643 | struct type *tmp = RB_ROOT(head); \ 644 | struct type *res = NULL; \ 645 | int comp; \ 646 | while (tmp) { \ 647 | comp = cmp(elm, tmp); \ 648 | if (comp < 0) { \ 649 | res = tmp; \ 650 | tmp = RB_LEFT(tmp, field); \ 651 | } \ 652 | else if (comp > 0) \ 653 | tmp = RB_RIGHT(tmp, field); \ 654 | else \ 655 | return (tmp); \ 656 | } \ 657 | return (res); \ 658 | } \ 659 | \ 660 | /* ARGSUSED */ \ 661 | attr struct type * \ 662 | name##_RB_NEXT(struct type *elm) \ 663 | { \ 664 | if (RB_RIGHT(elm, field)) { \ 665 | elm = RB_RIGHT(elm, field); \ 666 | while (RB_LEFT(elm, field)) \ 667 | elm = RB_LEFT(elm, field); \ 668 | } else { \ 669 | if (RB_PARENT(elm, field) && \ 670 | (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ 671 | elm = RB_PARENT(elm, field); \ 672 | else { \ 673 | while (RB_PARENT(elm, field) && \ 674 | (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ 675 | elm = RB_PARENT(elm, field); \ 676 | elm = RB_PARENT(elm, field); \ 677 | } \ 678 | } \ 679 | return (elm); \ 680 | } \ 681 | \ 682 | /* ARGSUSED */ \ 683 | attr struct type * \ 684 | name##_RB_PREV(struct type *elm) \ 685 | { \ 686 | if (RB_LEFT(elm, field)) { \ 687 | elm = RB_LEFT(elm, field); \ 688 | while (RB_RIGHT(elm, field)) \ 689 | elm = RB_RIGHT(elm, field); \ 690 | } else { \ 691 | if (RB_PARENT(elm, field) && \ 692 | (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ 693 | elm = RB_PARENT(elm, field); \ 694 | else { \ 695 | while (RB_PARENT(elm, field) && \ 696 | (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ 697 | elm = RB_PARENT(elm, field); \ 698 | elm = RB_PARENT(elm, field); \ 699 | } \ 700 | } \ 701 | return (elm); \ 702 | } \ 703 | \ 704 | attr struct type * \ 705 | name##_RB_MINMAX(struct name *head, int val) \ 706 | { \ 707 | struct type *tmp = RB_ROOT(head); \ 708 | struct type *parent = NULL; \ 709 | while (tmp) { \ 710 | parent = tmp; \ 711 | if (val < 0) \ 712 | tmp = RB_LEFT(tmp, field); \ 713 | else \ 714 | tmp = RB_RIGHT(tmp, field); \ 715 | } \ 716 | return (parent); \ 717 | } 718 | 719 | #define RB_NEGINF -1 720 | #define RB_INF 1 721 | 722 | #define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) 723 | #define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) 724 | #define RB_FIND(name, x, y) name##_RB_FIND(x, y) 725 | #define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) 726 | #define RB_NEXT(name, x, y) name##_RB_NEXT(y) 727 | #define RB_PREV(name, x, y) name##_RB_PREV(y) 728 | #define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) 729 | #define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) 730 | 731 | #define RB_FOREACH(x, name, head) \ 732 | for ((x) = RB_MIN(name, head); \ 733 | (x) != NULL; \ 734 | (x) = name##_RB_NEXT(x)) 735 | 736 | #define RB_FOREACH_FROM(x, name, y) \ 737 | for ((x) = (y); \ 738 | ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ 739 | (x) = (y)) 740 | 741 | #define RB_FOREACH_SAFE(x, name, head, y) \ 742 | for ((x) = RB_MIN(name, head); \ 743 | ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ 744 | (x) = (y)) 745 | 746 | #define RB_FOREACH_REVERSE(x, name, head) \ 747 | for ((x) = RB_MAX(name, head); \ 748 | (x) != NULL; \ 749 | (x) = name##_RB_PREV(x)) 750 | 751 | #define RB_FOREACH_REVERSE_FROM(x, name, y) \ 752 | for ((x) = (y); \ 753 | ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ 754 | (x) = (y)) 755 | 756 | #define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ 757 | for ((x) = RB_MAX(name, head); \ 758 | ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ 759 | (x) = (y)) 760 | 761 | #endif /* _SYS_TREE_H_ */ 762 | -------------------------------------------------------------------------------- /svnup.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2012-2019, John Mehr 3 | * Copyright (c) 2021 rofl0r 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. 26 | * 27 | * $FreeBSD$ 28 | * 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include /* MAXNAMLEN */ 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | #include "stringlist.h" 58 | 59 | #define SVNUP_VERSION "1.09" 60 | #define BUFFER_UNIT 4096 61 | #define COMMAND_BUFFER 32768 62 | #define COMMAND_BUFFER_THRESHOLD 32000 63 | 64 | #define LIT_LEN(S) (sizeof(S)-1) 65 | #define starts_with_lit(S1, S2) \ 66 | (strncmp(S1, S2, LIT_LEN(S2)) == 0) 67 | 68 | typedef struct { 69 | int socket_descriptor; 70 | enum { NONE, SVN, HTTP, HTTPS } protocol; 71 | enum svn_job { 72 | SVN_NONE = 0, 73 | SVN_CO, 74 | SVN_LOG, 75 | SVN_INFO, 76 | } job; 77 | SSL *ssl; 78 | SSL_CTX *ctx; 79 | char *address; 80 | uint16_t port; 81 | uint32_t revision; 82 | char *commit_author; 83 | char *commit_date; 84 | char *commit_msg; 85 | int family; 86 | char *root; 87 | char *trunk; 88 | char *branch; 89 | char *rev_root_stub; 90 | char *path_target; 91 | char *response; 92 | size_t response_length; 93 | uint32_t response_blocks; 94 | uint32_t response_groups; 95 | char *path_work; 96 | char *known_files; 97 | char *known_files_old; 98 | char *known_files_new; 99 | long known_files_size; 100 | int trim_tree; 101 | int extra_files; 102 | int verbosity; 103 | char inline_props; 104 | } connector; 105 | 106 | 107 | typedef struct { 108 | char md5[33]; 109 | char md5_checked; 110 | char download; 111 | char executable; 112 | char special; 113 | char *href; 114 | char *path; 115 | uint64_t raw_size; 116 | int64_t size; 117 | } file_node; 118 | 119 | 120 | struct tree_node { 121 | RB_ENTRY(tree_node) link; 122 | char *md5; 123 | char *path; 124 | }; 125 | 126 | 127 | /* Function Prototypes */ 128 | 129 | static char *md5sum(void*, size_t, char*); 130 | static int tree_node_compare(const struct tree_node *, const struct tree_node *); 131 | static void prune(connector *, char *); 132 | static char *find_response_end(int, char *, char *); 133 | static void find_local_files_and_directories(char *, const char *, int); 134 | static void reset_connection(connector *); 135 | static void send_command(connector *, const char *); 136 | static int check_command_success(int, char **, char **); 137 | static char *process_command_svn(connector *, const char *, unsigned int); 138 | static char *process_command_http(connector *, char *); 139 | static char *parse_xml_value(char *, char *, const char *); 140 | static void parse_response_group(connector *, char **, char **); 141 | static int parse_response_item(connector *, char *, int *, char **, char **); 142 | static file_node *new_file_node(file_node ***, int *, int *); 143 | static int save_file(char *, char *, char *, int, int); 144 | static void save_known_file_list(connector *, file_node **, int); 145 | static void create_directory(char *); 146 | static void process_report_svn(connector *, char *, file_node ***, int *, int *); 147 | static void process_report_http(connector *, file_node ***, int *file_count, int *); 148 | static void parse_additional_attributes(connector *, char *, char *, file_node *); 149 | static void get_files(connector *, char *, char *, file_node **, int, int); 150 | static void progress_indicator(connector *connection, char *, int, int); 151 | 152 | /* turn svn date string like "2020-11-10T09:23:51.711212Z" into "2020-11-10 09:23:51" */ 153 | static char* sanitize_svn_date(char *date) { 154 | char *temp = temp; 155 | temp = strchr(date, 'T'); 156 | *temp++ = ' '; 157 | temp = strchr(temp, '.'); 158 | *temp = 'Z'; 159 | *temp = 0; 160 | return temp; 161 | } 162 | 163 | static char* 164 | http_extract_header_value(char* response, const char* name, char* buf, size_t buflen) 165 | { 166 | char *line, *p; 167 | line = p = response; 168 | size_t l = strlen(name); 169 | while(1) { 170 | p = strstr(p, "\r\n"); 171 | if(!p) break; 172 | if(!strncmp(line, name, l) && line[l] == ':' && line[l+1] == ' ') { 173 | intptr_t ll = p - line - l - 2; 174 | if(ll > 0 && ll+1 < buflen) { 175 | memcpy(buf, line + l + 2, ll); 176 | buf[ll] = 0; 177 | return buf; 178 | } 179 | return 0; 180 | } 181 | p += 2; 182 | line = p; 183 | } 184 | return 0; 185 | } 186 | 187 | static char* strip_rev_root_stub(connector *connection, char* path) { 188 | char *tmp = path; 189 | if(connection->rev_root_stub && strstr(path, connection->rev_root_stub) == path) { 190 | tmp += strlen(connection->rev_root_stub); 191 | if(*tmp == '/')++tmp; 192 | while(isdigit(*tmp))++tmp; 193 | } 194 | return tmp; 195 | } 196 | 197 | /* 198 | * md5sum 199 | * 200 | * Function that returns hexadecimal md5 hash in out. 201 | * out needs to be char[MD5_DIGEST_LENGTH*2+1]. 202 | */ 203 | static char* 204 | md5sum(void *data, size_t dlen, char *out) 205 | { 206 | MD5_CTX md5_context; 207 | unsigned char md5_digest[MD5_DIGEST_LENGTH]; 208 | size_t i, j; 209 | 210 | MD5_Init(&md5_context); 211 | MD5_Update(&md5_context, data, dlen); 212 | MD5_Final(md5_digest, &md5_context); 213 | 214 | for (i = 0, j = 0; i < MD5_DIGEST_LENGTH; i++, j+=2) 215 | sprintf(out+j, "%02x", md5_digest[i]); 216 | 217 | return out; 218 | } 219 | 220 | /* 221 | * tree_node_compare 222 | * 223 | * Function that informs the Red-Black tree functions how to sort keys. 224 | */ 225 | 226 | static int 227 | tree_node_compare(const struct tree_node *a, const struct tree_node *b) 228 | { 229 | return (strcmp(a->path, b->path)); 230 | } 231 | 232 | /* 233 | * tree_node_free 234 | * 235 | * Function that frees the memory used by a tree node. 236 | */ 237 | 238 | static void 239 | tree_node_free(struct tree_node *node) 240 | { 241 | if (node->md5 != NULL) 242 | free(node->md5); 243 | 244 | if (node->path != NULL) 245 | free(node->path); 246 | 247 | free(node); 248 | } 249 | 250 | 251 | static RB_HEAD(tree_known_files, tree_node) known_files = RB_INITIALIZER(&known_files); 252 | RB_PROTOTYPE(tree_known_files, tree_node, link, tree_node_compare) 253 | RB_GENERATE(tree_known_files, tree_node, link, tree_node_compare) 254 | 255 | static RB_HEAD(tree_local_files, tree_node) local_files = RB_INITIALIZER(&local_files); 256 | RB_PROTOTYPE(tree_local_files, tree_node, link, tree_node_compare) 257 | RB_GENERATE(tree_local_files, tree_node, link, tree_node_compare) 258 | 259 | static RB_HEAD(tree_local_directories, tree_node) local_directories = RB_INITIALIZER(&local_directories); 260 | RB_PROTOTYPE(tree_local_directories, tree_node, link, tree_node_compare) 261 | RB_GENERATE(tree_local_directories, tree_node, link, tree_node_compare) 262 | 263 | 264 | /* 265 | * prune 266 | * 267 | * Procedure that removes the file passed in (and it's parent folder if it's empty). 268 | */ 269 | 270 | static void 271 | prune(connector *connection, char *path_target) 272 | { 273 | struct stat local; 274 | char *temp, *temp_file; 275 | size_t length; 276 | 277 | length = strlen(path_target) + strlen(connection->path_target) + 2; 278 | 279 | if ((temp_file = (char *)malloc(length)) == NULL) 280 | err(EXIT_FAILURE, "prune temp_file malloc"); 281 | 282 | snprintf(temp_file, length, "%s%s", connection->path_target, path_target); 283 | 284 | if (stat(temp_file, &local) != -1) { 285 | if (connection->verbosity) 286 | printf(" - %s\n", temp_file); 287 | 288 | if ((S_ISREG(local.st_mode)) || (S_ISLNK(local.st_mode))) { 289 | if (remove(temp_file) != 0) { 290 | err(EXIT_FAILURE, "Cannot remove %s", temp_file); 291 | } else { 292 | /* Isolate the parent directory in the path name 293 | * and try and remove it. Failure is ok. */ 294 | 295 | if ((temp = strrchr(temp_file, '/')) != NULL) { 296 | *temp = '\0'; 297 | rmdir(temp_file); 298 | } 299 | } 300 | } 301 | 302 | if (S_ISDIR(local.st_mode)) 303 | rmdir(temp_file); 304 | } 305 | 306 | free(temp_file); 307 | } 308 | 309 | 310 | /* 311 | * find_local_files_and_directories 312 | * 313 | * Procedure that recursively finds and adds local files and directories to 314 | * separate red-black trees. 315 | */ 316 | 317 | static void 318 | find_local_files_and_directories(char *path_base, const char *path_target, int include_files) 319 | { 320 | DIR *dp; 321 | struct stat local; 322 | struct dirent *de; 323 | struct tree_node *data; 324 | char *temp_file; 325 | size_t length, len; 326 | 327 | length = strlen(path_base) + strlen(path_target) + MAXNAMLEN + 3; 328 | 329 | if ((temp_file = (char *)malloc(length)) == NULL) 330 | err(EXIT_FAILURE, "find_local_files_and_directories temp_file malloc"); 331 | 332 | snprintf(temp_file, length, "%s%s", path_base, path_target); 333 | 334 | if (lstat(temp_file, &local) != -1) { 335 | if (S_ISDIR(local.st_mode)) { 336 | 337 | /* Keep track of the local directories, ignoring path_base. */ 338 | 339 | if (strlen(path_target)) { 340 | data = (struct tree_node *)malloc(sizeof(struct tree_node)); 341 | data->path = strdup(temp_file); 342 | data->md5 = NULL; 343 | 344 | RB_INSERT(tree_local_directories, &local_directories, data); 345 | } 346 | 347 | /* Recursively process the contents of the directory. */ 348 | 349 | if ((dp = opendir(temp_file)) != NULL) { 350 | while ((de = readdir(dp)) != NULL) { 351 | len = strlen(de->d_name); 352 | 353 | if ((len == 1) && (strcmp(de->d_name, "." ) == 0)) 354 | continue; 355 | 356 | if ((len == 2) && (strcmp(de->d_name, "..") == 0)) 357 | continue; 358 | 359 | snprintf(temp_file, 360 | length, 361 | "%s/%s", 362 | path_target, 363 | de->d_name); 364 | 365 | find_local_files_and_directories(path_base, temp_file, include_files); 366 | } 367 | 368 | closedir(dp); 369 | } 370 | } else { 371 | if (include_files) { 372 | data = (struct tree_node *)malloc(sizeof(struct tree_node)); 373 | data->path = strdup(path_target); 374 | data->md5 = NULL; 375 | 376 | RB_INSERT(tree_local_files, &local_files, data); 377 | } 378 | } 379 | } 380 | 381 | free(temp_file); 382 | } 383 | 384 | 385 | /* 386 | * find_response_end 387 | * 388 | * Function that locates the end of a command response in the response stream. For the SVN 389 | * protocol, it counts opening and closing parenthesis and for HTTP/S, it looks for a pair 390 | * of CRLFs. 391 | */ 392 | 393 | static char * 394 | find_response_end(int protocol, char *start, char *end) 395 | { 396 | int count = 0; 397 | 398 | if (protocol == SVN) 399 | do { 400 | count += (*start == '(' ? 1 : (*start == ')' ? -1 : 0)); 401 | } 402 | while ((*start != '\0') && (start++ < end) && (count > 0)); 403 | 404 | if (protocol >= HTTP) 405 | start = strstr(start, "\r\n\r\n") + 4; 406 | 407 | return (start); 408 | } 409 | 410 | 411 | /* 412 | * reset_connection 413 | * 414 | * Procedure that (re)establishes a connection with the server. 415 | */ 416 | 417 | static void 418 | reset_connection(connector *connection) 419 | { 420 | struct addrinfo hints = { 421 | .ai_family = connection->family, 422 | .ai_socktype = SOCK_STREAM, 423 | }, *start, *temp, *gai; 424 | int error, option; 425 | char type[10]; 426 | 427 | if (connection->socket_descriptor != -1) 428 | if (close(connection->socket_descriptor) != 0) 429 | if (errno != EBADF) err(EXIT_FAILURE, "close_connection"); 430 | 431 | snprintf(type, sizeof(type), "%d", connection->port); 432 | 433 | if ((error = getaddrinfo(connection->address, type, &hints, &start))) 434 | errx(EXIT_FAILURE, "%s", gai_strerror(error)); 435 | 436 | gai = start; 437 | 438 | connection->socket_descriptor = -1; 439 | while (start) { 440 | temp = start; 441 | 442 | if (connection->socket_descriptor < 0) { 443 | if ((connection->socket_descriptor = socket(temp->ai_family, temp->ai_socktype, temp->ai_protocol)) < 0) 444 | err(EXIT_FAILURE, "socket failure"); 445 | 446 | if (connect(connection->socket_descriptor, temp->ai_addr, temp->ai_addrlen) < 0) 447 | err(EXIT_FAILURE, "connect failure"); 448 | } 449 | 450 | start = temp->ai_next; 451 | } 452 | 453 | if(gai) freeaddrinfo(gai); 454 | 455 | if (connection->protocol == HTTPS) { 456 | if (SSL_library_init() == 0) 457 | err(EXIT_FAILURE, "reset_connection: SSL_library_init"); 458 | 459 | SSL_load_error_strings(); 460 | connection->ctx = SSL_CTX_new(SSLv23_client_method()); 461 | SSL_CTX_set_mode(connection->ctx, SSL_MODE_AUTO_RETRY); 462 | SSL_CTX_set_options(connection->ctx, SSL_OP_ALL | SSL_OP_NO_TICKET); 463 | 464 | if ((connection->ssl = SSL_new(connection->ctx)) == NULL) 465 | err(EXIT_FAILURE, "reset_connection: SSL_new"); 466 | 467 | SSL_set_fd(connection->ssl, connection->socket_descriptor); 468 | while ((error = SSL_connect(connection->ssl)) == -1) 469 | fprintf(stderr, "SSL_connect error:%d\n", SSL_get_error(connection->ssl, error)); 470 | } 471 | 472 | option = 1; 473 | 474 | if (setsockopt(connection->socket_descriptor, SOL_SOCKET, SO_KEEPALIVE, &option, sizeof(option))) 475 | err(EXIT_FAILURE, "setsockopt SO_KEEPALIVE error"); 476 | 477 | option = COMMAND_BUFFER; 478 | 479 | if (setsockopt(connection->socket_descriptor, SOL_SOCKET, SO_SNDBUF, &option, sizeof(option))) 480 | err(EXIT_FAILURE, "setsockopt SO_SNDBUF error"); 481 | 482 | if (setsockopt(connection->socket_descriptor, SOL_SOCKET, SO_RCVBUF, &option, sizeof(option))) 483 | err(EXIT_FAILURE, "setsockopt SO_RCVBUF error"); 484 | } 485 | 486 | 487 | /* 488 | * send_command 489 | * 490 | * Procedure that sends commands to the http/svn server. 491 | */ 492 | 493 | static void 494 | send_command(connector *connection, const char *command) 495 | { 496 | size_t bytes_to_write, total_bytes_written; 497 | ssize_t bytes_written; 498 | 499 | if (command) { 500 | total_bytes_written = 0; 501 | bytes_to_write = strlen(command); 502 | 503 | if (connection->verbosity > 2) 504 | fprintf(stdout, "<< %zu bytes\n%s", bytes_to_write, command); 505 | 506 | while (total_bytes_written < bytes_to_write) { 507 | if (connection->protocol == HTTPS) 508 | bytes_written = SSL_write( 509 | connection->ssl, 510 | command + total_bytes_written, 511 | bytes_to_write - total_bytes_written); 512 | else 513 | bytes_written = write( 514 | connection->socket_descriptor, 515 | command + total_bytes_written, 516 | bytes_to_write - total_bytes_written); 517 | 518 | if (bytes_written <= 0) { 519 | if ((bytes_written < 0) && ((errno == EINTR) || (errno == 0))) { 520 | continue; 521 | } else { 522 | err(EXIT_FAILURE, "send command"); 523 | } 524 | } 525 | 526 | total_bytes_written += bytes_written; 527 | } 528 | } 529 | } 530 | 531 | 532 | /* 533 | * check_command_success 534 | * 535 | * Function that makes sure a failure response has not been sent from the svn server. 536 | */ 537 | 538 | static int 539 | check_command_success(int protocol, char **start, char **end) 540 | { 541 | int fail = 0; 542 | char *response = *start; 543 | 544 | if (protocol == SVN) { 545 | if (starts_with_lit(*start, "( success ( ( ) 0: ) ) ( failure")) 546 | fail = 1; 547 | 548 | else if (starts_with_lit(*start, "( success ( ) ) ( failure")) 549 | fail = 1; 550 | 551 | if (!fail) { 552 | while (**start == ' ') (*start)++; 553 | 554 | if (starts_with_lit(*start, "( success ")) { 555 | if (starts_with_lit(*start, "( success ( ( ) 0: ) )")) 556 | *start += LIT_LEN("( success ( ( ) 0: ) )")+1; 557 | *end = find_response_end(protocol, *start, *end) + 1; 558 | } 559 | else fail = 1; 560 | } 561 | } 562 | 563 | if (protocol >= HTTP) { 564 | if (!starts_with_lit(*start, "HTTP/1.1 ")) 565 | fail = 1; 566 | else { 567 | *start += LIT_LEN("HTTP/1.1 "); 568 | if(**start != '2') fail = 1; 569 | else { 570 | *start = strstr(*start, "\r\n\r\n"); 571 | if (*start) *start += 4; else fail = 1; 572 | } 573 | } 574 | } 575 | 576 | if (fail) { 577 | if(protocol == SVN || !strstr(response, "xml version=")) 578 | fprintf(stderr, "\nCommand Failure: %s\n", response); 579 | else { 580 | char *ec = parse_xml_value(*start, *end, "m:human-readable"); 581 | if(ec) { fprintf(stderr, "\n%s\n", ec); free(ec); } 582 | else fprintf(stderr, "\nCommand Failure: %s\n", response); 583 | } 584 | } 585 | 586 | return (fail); 587 | } 588 | 589 | 590 | /* 591 | * process_command_svn 592 | * 593 | * Function that sends a command set to the svn server and parses its response to make 594 | * sure that the expected number of response strings have been received. 595 | */ 596 | 597 | static char * 598 | process_command_svn(connector *connection, const char *command, unsigned int expected_bytes) 599 | { 600 | ssize_t bytes_read; 601 | int count, ok; 602 | unsigned int group, position, try; 603 | char *check, input[BUFFER_UNIT + 1]; 604 | 605 | try = 0; 606 | retry: 607 | 608 | send_command(connection, command); 609 | 610 | count = position = ok = group = connection->response_length = 0; 611 | 612 | do { 613 | bzero(input, BUFFER_UNIT + 1); 614 | 615 | bytes_read = read(connection->socket_descriptor, input, BUFFER_UNIT); 616 | 617 | if (bytes_read <= 0) { 618 | if ((errno == EINTR) || (errno == 0)) continue; 619 | 620 | if (++try > 5) 621 | errx(EXIT_FAILURE, "Error in svn stream. Quitting."); 622 | 623 | if (try > 1) 624 | fprintf(stderr, "Error in svn stream, retry #%d\n", try); 625 | 626 | goto retry; 627 | } 628 | 629 | input[bytes_read] = 0; 630 | if (connection->verbosity > 3) 631 | fprintf(stdout, "<< %s\n", input); 632 | 633 | connection->response_length += bytes_read; 634 | 635 | /* always allocate at least BUFFER_UNIT bytes more than we actually need 636 | because there's some wacky code in get_files() - see FIXME comment 637 | before the memmove() there */ 638 | size_t max = connection->response_length + BUFFER_UNIT; 639 | if(expected_bytes + BUFFER_UNIT > max) max = expected_bytes + BUFFER_UNIT; 640 | if (max >= connection->response_blocks * BUFFER_UNIT) { 641 | do connection->response_blocks += (connection->response_blocks/2); 642 | while(max >= connection->response_blocks * BUFFER_UNIT); 643 | 644 | connection->response = realloc( 645 | connection->response, 646 | connection->response_blocks * BUFFER_UNIT + 1); 647 | 648 | if (connection->response == NULL) 649 | err(EXIT_FAILURE, "process_command_svn realloc"); 650 | } 651 | 652 | if (expected_bytes == 0) { 653 | if (input[1] == '\0') { 654 | connection->response[position++] = input[0]; 655 | continue; 656 | } 657 | 658 | if (connection->verbosity > 3) 659 | fprintf(stdout, "==========\n>> Response Parse:\n"); 660 | 661 | check = input; 662 | if ((count == 0) && (input[0] == ' ')) 663 | *check++ = '\0'; 664 | 665 | do { 666 | 667 | if (*check == ')') 668 | --count; 669 | else if(*check == '(') { 670 | /* trying to skip size-annotated block, which in case of 671 | a commit message may contain unbalanced parens */ 672 | int skip = 0; 673 | char *q, *p = check+1, *e = input + bytes_read; 674 | if(p+2 < e && p[0] == ' ' && isdigit(p[1])) { 675 | q = p+1; 676 | while(q < e && isdigit(*q)) ++q; 677 | if(q < e && *q == ':') { 678 | skip = atoi(p)+1; 679 | check = q; 680 | } 681 | } 682 | if(skip) { 683 | if(check+skip < input + bytes_read) { 684 | check += skip; 685 | } else if (connection->verbosity > 3) { 686 | fprintf(stderr, "couldn't skip %d bytes", skip); 687 | } 688 | } 689 | ++count; 690 | } 691 | 692 | if (connection->verbosity > 3) 693 | fprintf(stderr, "%d", count); 694 | 695 | if (count == 0) { 696 | group++; 697 | check++; 698 | if (check < input + bytes_read) { 699 | if (*check == ' ') 700 | *check = '\0'; 701 | 702 | if (*check != '\0') 703 | fprintf(stderr, "oops: %d %c\n", *check, *check); 704 | 705 | char *q = check + 1; 706 | while(q < input + bytes_read && *q != '(') ++q; 707 | check = q-1; 708 | } 709 | } 710 | } 711 | while (++check < input + bytes_read); 712 | } 713 | 714 | memcpy(connection->response + position, input, bytes_read + 1); 715 | position += bytes_read; 716 | 717 | if ((expected_bytes == 0) && (connection->verbosity > 3)) 718 | fprintf(stderr, ". = %d %d\n", group, connection->response_groups); 719 | 720 | if (group >= connection->response_groups) 721 | ok = 1; 722 | 723 | if (position == expected_bytes) 724 | ok = 1; 725 | 726 | if ((expected_bytes > 0) && (connection->response[0] == ' ') && (position == expected_bytes + 1)) 727 | ok = 1; 728 | } 729 | while (!ok); 730 | 731 | if ((expected_bytes == 0) && (connection->verbosity > 2)) 732 | fprintf(stdout, "==========\n>> Response:\n%s", connection->response); 733 | 734 | connection->response[position] = '\0'; 735 | 736 | return (connection->response); 737 | } 738 | 739 | 740 | /* 741 | * process_command_http 742 | * 743 | * Function that sends a command set to the http server and parses its response to make 744 | * sure that the expected number of response bytes have been received. 745 | */ 746 | 747 | static char * 748 | process_command_http(connector *connection, char *command) 749 | { 750 | int bytes_read, chunk, chunked_transfer, first_chunk, gap, read_more, spread; 751 | unsigned int groups, offset, try; 752 | char *begin, *end, input[BUFFER_UNIT + 1], *marker1, *marker2, *temp, hex_chunk[32]; 753 | 754 | try = 0; 755 | retry: 756 | 757 | chunked_transfer = -1; 758 | connection->response_length = chunk = groups = 0; 759 | offset = read_more = 0; 760 | first_chunk = 1; 761 | begin = end = marker1 = marker2 = temp = NULL; 762 | 763 | bzero(connection->response, connection->response_blocks * BUFFER_UNIT + 1); 764 | bzero(input, BUFFER_UNIT + 1); 765 | 766 | if (try || connection->socket_descriptor == -1) 767 | reset_connection(connection); 768 | send_command(connection, command); 769 | 770 | while (groups < connection->response_groups) { 771 | spread = connection->response_length - offset; 772 | 773 | if (spread <= 0) 774 | read_more = 1; 775 | 776 | /* Sometimes the read returns only part of the next offset, so 777 | * if there were less than five bytes read, keep reading to get 778 | * the remainder of the offset. */ 779 | 780 | if ((chunked_transfer == 1) && (spread <= 5)) 781 | read_more = 1; 782 | 783 | if ((chunked_transfer == 0) && (spread == 0) && (connection->response_groups - groups == 1)) 784 | break; 785 | 786 | if (read_more) { 787 | if (connection->protocol == HTTPS) 788 | bytes_read = SSL_read( 789 | connection->ssl, 790 | input, 791 | BUFFER_UNIT); 792 | else 793 | bytes_read = read( 794 | connection->socket_descriptor, 795 | input, 796 | BUFFER_UNIT); 797 | 798 | if (connection->response_length + bytes_read > connection->response_blocks * BUFFER_UNIT) { 799 | while(connection->response_length + bytes_read > connection->response_blocks * BUFFER_UNIT) 800 | connection->response_blocks += (connection->response_blocks/2); 801 | 802 | #define SAVE_VAR(X) \ 803 | intptr_t X ## _offset; \ 804 | int was_null_ ## X = 0; \ 805 | if(X) X ## _offset = X - connection->response; \ 806 | else was_null_ ## X = 1; 807 | 808 | SAVE_VAR(marker2); 809 | SAVE_VAR(begin); 810 | SAVE_VAR(end); 811 | 812 | connection->response = (char *)realloc( 813 | connection->response, 814 | connection->response_blocks * BUFFER_UNIT + 1); 815 | 816 | if (connection->response == NULL) 817 | err(EXIT_FAILURE, "process_command_http realloc"); 818 | 819 | #define RESTORE_VAR(X) \ 820 | if (!was_null_ ## X) \ 821 | X = connection->response + X ## _offset; 822 | 823 | RESTORE_VAR(marker2); 824 | RESTORE_VAR(begin); 825 | RESTORE_VAR(end); 826 | } 827 | 828 | if (bytes_read < 0) { 829 | if ((errno == EINTR) || (errno == 0)) 830 | continue; 831 | 832 | check_tries_and_retry:; 833 | if (++try > 5) 834 | errx(EXIT_FAILURE, "Error in http stream. Quitting."); 835 | 836 | if (try > 1) 837 | fprintf(stderr, "Error in http stream, retry #%d\n", try); 838 | 839 | goto retry; 840 | } 841 | 842 | if (bytes_read == 0) { 843 | if(connection->response_length == 0) goto check_tries_and_retry; 844 | break; 845 | } 846 | 847 | memcpy(connection->response + connection->response_length, input, bytes_read + 1); 848 | connection->response_length += bytes_read; 849 | connection->response[connection->response_length] = '\0'; 850 | read_more = 0; 851 | spread = connection->response_length - offset; 852 | } 853 | 854 | if ((chunked_transfer == 0) && (spread >= 0)) { 855 | chunked_transfer = -1; 856 | groups++; 857 | } 858 | 859 | if (chunked_transfer == -1) { 860 | begin = connection->response + offset; 861 | 862 | if ((begin = strstr(begin, "HTTP/1.1 ")) == NULL) { 863 | read_more = 1; 864 | continue; 865 | } 866 | 867 | if ((end = strstr(begin, "\r\n\r\n")) == NULL) { 868 | read_more = 1; 869 | continue; 870 | } 871 | 872 | if(strstr(begin, "DAV: http://subversion.tigris.org/xmlns/dav/svn/inline-props")) 873 | connection->inline_props = 1; 874 | 875 | end += 4; 876 | 877 | offset += (end - begin); 878 | groups++; 879 | 880 | marker1 = strstr(begin, "Content-Length: "); 881 | marker2 = strstr(begin, "Transfer-Encoding: chunked"); 882 | 883 | if (marker1) chunked_transfer = 0; 884 | if (marker2) chunked_transfer = 1; 885 | 886 | if ((marker1) && (marker2)) 887 | chunked_transfer = (marker1 < marker2) ? 0 : 1; 888 | 889 | if (chunked_transfer == 0) { 890 | chunk = strtol(marker1 + 16, (char **)NULL, 10); 891 | 892 | if (chunk < 0) 893 | errx(EXIT_FAILURE, "process_command_http: Bad stream data"); 894 | 895 | offset += chunk; 896 | if (connection->response_length > offset) { 897 | chunked_transfer = -1; 898 | groups++; 899 | } 900 | } 901 | 902 | if (chunked_transfer == 1) { 903 | chunk = 0; 904 | marker2 = end; 905 | } 906 | } 907 | 908 | while ((chunked_transfer == 1) && ((end = strstr(marker2, "\r\n")) != NULL)) { 909 | chunk = strtol(marker2, (char **)NULL, 16); 910 | marker2 -= 2; 911 | 912 | if (chunk < 0) 913 | errx(EXIT_FAILURE, "process_command_http: Bad stream data "); 914 | 915 | snprintf(hex_chunk, sizeof(hex_chunk), "\r\n%x\r\n", chunk); 916 | gap = strlen(hex_chunk); 917 | 918 | if (marker2 + chunk + gap > connection->response + connection->response_length) { 919 | marker2 += 2; 920 | read_more = 1; 921 | break; 922 | } 923 | 924 | if (first_chunk) { 925 | first_chunk = 0; 926 | chunk += gap; 927 | } 928 | else { 929 | /* Remove the chunk from the buffer. */ 930 | 931 | memmove(marker2, 932 | marker2 + gap, 933 | connection->response_length - (marker2 - connection->response)); 934 | 935 | connection->response_length -= gap; 936 | } 937 | 938 | /* Move the offset to the end of the chunk. */ 939 | 940 | offset += chunk; 941 | marker2 += chunk + 2; 942 | 943 | if (chunk == 0) { 944 | chunked_transfer = -1; 945 | groups++; 946 | } 947 | } 948 | 949 | if (connection->verbosity > 2) 950 | fprintf(stderr, "\rBytes read: %zd, Bytes expected: %d, g:%d, rg:%d", 951 | connection->response_length, 952 | offset, 953 | groups, 954 | connection->response_groups); 955 | } 956 | 957 | if (connection->verbosity > 2) 958 | fprintf(stderr, "\rBytes read: %zd, Bytes expected: %d, g:%d, rg:%d", 959 | connection->response_length, 960 | offset, 961 | groups, 962 | connection->response_groups); 963 | 964 | if (connection->verbosity > 2) 965 | fprintf(stderr, "\n"); 966 | 967 | if (connection->verbosity > 3) 968 | fprintf(stderr, "==========\n%s\n==========\n", connection->response); 969 | 970 | if(!strstr(connection->response, "HTTP/1.1 ")) 971 | errx(EXIT_FAILURE, "unexpected response from HTTP server:\n%s", connection->response); 972 | 973 | return (connection->response); 974 | } 975 | 976 | 977 | /* 978 | * parse_xml_value 979 | * 980 | * Function that returns the text found between the opening and closing tags passed in. 981 | */ 982 | 983 | static char * 984 | parse_xml_value(char *start, char *end, const char *tag) 985 | { 986 | size_t tag_length; 987 | char *data_end, *data_start, *end_tag, temp_end, *value; 988 | 989 | value = NULL; 990 | temp_end = *end; 991 | *end = '\0'; 992 | 993 | tag_length = strlen(tag) + 4; 994 | if ((end_tag = (char *)malloc(tag_length)) == NULL) 995 | err(EXIT_FAILURE, "parse_xml_value end_tag malloc"); 996 | 997 | snprintf(end_tag, tag_length, "", tag); 998 | 999 | if ((data_start = strstr(start, tag))) { 1000 | if ((data_start = strchr(data_start, '>'))) { 1001 | data_start++; 1002 | data_end = strstr(data_start, end_tag); 1003 | 1004 | if (data_end) { 1005 | if ((value = (char *)malloc(data_end - data_start + 1)) == NULL) 1006 | err(EXIT_FAILURE, "parse_xml_value value malloc"); 1007 | 1008 | memcpy(value, data_start, data_end - data_start); 1009 | value[data_end - data_start] = '\0'; 1010 | } 1011 | } 1012 | } 1013 | 1014 | free(end_tag); 1015 | *end = temp_end; 1016 | 1017 | return (value); 1018 | } 1019 | 1020 | 1021 | /* 1022 | * parse_response_group 1023 | * 1024 | * Procedure that isolates the next response group from the response stream. 1025 | */ 1026 | 1027 | static void 1028 | parse_response_group(connector *connection, char **start, char **end) 1029 | { 1030 | if (connection->protocol == SVN) 1031 | *end = find_response_end(connection->protocol, *start, *end); 1032 | 1033 | if (connection->protocol >= HTTP) { 1034 | *end = strstr(*start, ""); 1035 | if (*end != NULL) *end += 16; 1036 | else errx(EXIT_FAILURE, "Error in http stream: %s\n", *start); 1037 | } 1038 | 1039 | **end = '\0'; 1040 | } 1041 | 1042 | 1043 | /* 1044 | * parse_response_item 1045 | * 1046 | * Function that isolates the next response from the current response group. 1047 | */ 1048 | 1049 | static int 1050 | parse_response_item(connector *connection, char *end, int *count, char **item_start, char **item_end) 1051 | { 1052 | int c, has_entries, ok; 1053 | 1054 | c = has_entries = 0; 1055 | ok = 1; 1056 | 1057 | if (connection->protocol == SVN) { 1058 | if (*count == '\0') { 1059 | while ((c < 3) && (*item_start < end)) { 1060 | c += (**item_start == '(' ? 1 : (**item_start == ')' ? -1 : 0)); 1061 | if (**item_start == ':') has_entries++; 1062 | (*item_start)++; 1063 | } 1064 | 1065 | (*item_start) += 5; 1066 | *item_end = *item_start; 1067 | } 1068 | 1069 | c = 1; 1070 | (*item_end)++; 1071 | 1072 | while ((c > 0) && (*item_end < end)) { 1073 | (*item_end)++; 1074 | c += (**item_end == '(' ? 1 : (**item_end == ')' ? -1 : 0)); 1075 | if (**item_end == ':') has_entries++; 1076 | } 1077 | 1078 | (*item_end)++; 1079 | **item_end = '\0'; 1080 | } 1081 | 1082 | if (connection->protocol >= HTTP) { 1083 | *item_end = strstr(*item_start, ""); 1084 | 1085 | if (*item_end != NULL) { 1086 | *item_end += 13; 1087 | **item_end = '\0'; 1088 | has_entries = 1; 1089 | } else ok = 0; 1090 | } 1091 | 1092 | if (!has_entries) ok = 0; 1093 | 1094 | (*count)++; 1095 | 1096 | return (ok); 1097 | } 1098 | 1099 | /* checking whether the md5 of file as retrieved from online repo 1100 | matches the value in known_files list. if the file isn't in 1101 | known_files then there's no point checking against the 1102 | filesystem either, as it shouldn't exist there. 1103 | the only case where it could be interesting to check against 1104 | a filesystem copy would be the case of a huge repo checked 1105 | out with a different client (i.e. no known_files file exists 1106 | yet), and the user being unwilling to check 1107 | out every single file again - but in that case he could just 1108 | produce the file known_files with a script that runs md5sum 1109 | over all files. 1110 | */ 1111 | static void check_md5(connector *connection, file_node *file) { 1112 | char buf[4096]; 1113 | struct tree_node *data, *next; 1114 | if(file->md5[0] && !file->md5_checked) { 1115 | file->md5_checked = 1; 1116 | file->download = 1; /* default to "md5 doesn't match local file" */ 1117 | snprintf(buf, sizeof buf, "%s", 1118 | strip_rev_root_stub(connection, file->path)); 1119 | 1120 | for (data = RB_MIN(tree_known_files, &known_files); data != NULL; data = next) { 1121 | if(!strcmp(data->path, buf)) { 1122 | if(!memcmp(data->md5, file->md5, 32)) { 1123 | file->download = 0; 1124 | return; 1125 | } 1126 | /* file encountered in known_files, but md5 mismatch */ 1127 | break; 1128 | } 1129 | next = RB_NEXT(tree_known_files, head, data); 1130 | } 1131 | } 1132 | } 1133 | 1134 | /* 1135 | * new_file_node 1136 | * 1137 | * Function that allocates a new file_node and expands the dynamic 1138 | * array that stores file_nodes. 1139 | */ 1140 | 1141 | static file_node * 1142 | new_file_node(file_node ***file, int *file_count, int *file_max) 1143 | { 1144 | file_node *node = calloc(1, sizeof(file_node)); 1145 | 1146 | if (node == NULL) 1147 | err(EXIT_FAILURE, "new_file_node node malloc"); 1148 | 1149 | (*file)[*file_count] = node; 1150 | 1151 | if (++(*file_count) == *file_max) { 1152 | *file_max += BUFFER_UNIT; 1153 | 1154 | if ((*file = (file_node **)realloc(*file, *file_max * sizeof(file_node **))) == NULL) 1155 | err(EXIT_FAILURE, "new_file_node file realloc"); 1156 | } 1157 | 1158 | return (node); 1159 | } 1160 | 1161 | /* 1162 | * save_file 1163 | * 1164 | * Procedure that saves a file. 1165 | */ 1166 | 1167 | static int 1168 | save_file(char *filename, char *start, char *end, int executable, int special) 1169 | { 1170 | struct stat local; 1171 | int fd, saved; 1172 | char *tag; 1173 | 1174 | saved = 0; 1175 | 1176 | if (special) { 1177 | if (starts_with_lit(start, "link ")) { 1178 | *end = '\0'; 1179 | 1180 | if (stat(filename, &local) == 0) 1181 | if (remove(filename) != 0) 1182 | errx(EXIT_FAILURE, "Please remove %s manually and restart svnup", filename); 1183 | 1184 | if (symlink(start + 5, filename)) { 1185 | err(EXIT_FAILURE, "Cannot link %s -> %s", start + 5, filename); 1186 | 1187 | saved = 1; 1188 | } 1189 | } 1190 | } else { 1191 | if ((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, executable ? 0755 : 0644)) == -1) 1192 | err(EXIT_FAILURE, "write file failure %s", filename); 1193 | 1194 | write(fd, start, end - start); 1195 | close(fd); 1196 | 1197 | saved = 1; 1198 | } 1199 | 1200 | return (saved); 1201 | } 1202 | 1203 | 1204 | /* 1205 | * save_known_file_list 1206 | * 1207 | * Procedure that saves the list of files known to be in the repository. 1208 | */ 1209 | 1210 | static void 1211 | save_known_file_list(connector *connection, file_node **file, int file_count) 1212 | { 1213 | struct tree_node find, *found; 1214 | int fd, x; 1215 | 1216 | if ((fd = open(connection->known_files_new, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) 1217 | err(EXIT_FAILURE, "write file failure %s", connection->known_files_new); 1218 | 1219 | for (x = 0; x < file_count; x++) { 1220 | write(fd, file[x]->md5, strlen(file[x]->md5)); 1221 | write(fd, "\t", 1); 1222 | char* ftmp = strip_rev_root_stub(connection, file[x]->path); 1223 | write(fd, ftmp, strlen(ftmp)); 1224 | write(fd, "\n", 1); 1225 | 1226 | /* If the file exists in the red-black trees, remove it. */ 1227 | 1228 | find.path = ftmp; 1229 | 1230 | if ((found = RB_FIND(tree_known_files, &known_files, &find)) != NULL) 1231 | tree_node_free(RB_REMOVE(tree_known_files, &known_files, found)); 1232 | 1233 | if ((found = RB_FIND(tree_local_files, &local_files, &find)) != NULL) 1234 | tree_node_free(RB_REMOVE(tree_local_files, &local_files, found)); 1235 | 1236 | if (file[x]->href) 1237 | free(file[x]->href); 1238 | 1239 | free(file[x]->path); 1240 | free(file[x]); 1241 | file[x] = NULL; 1242 | } 1243 | 1244 | close(fd); 1245 | } 1246 | 1247 | 1248 | static int protocol_from_str(char* line, connector *connection) { 1249 | if (strncmp(line, "svn", 3) == 0) { 1250 | connection->protocol = SVN; 1251 | connection->port = 3690; 1252 | } else 1253 | 1254 | if (strncmp(line, "https", 5) == 0) { 1255 | connection->protocol = HTTPS; 1256 | connection->port = 443; 1257 | } else 1258 | 1259 | if (strncmp(line, "http", 4) == 0) { 1260 | connection->protocol = HTTP; 1261 | connection->port = 80; 1262 | } else { 1263 | connection->protocol = NONE; 1264 | return 0; 1265 | } 1266 | 1267 | return 1; 1268 | } 1269 | 1270 | /* 1271 | * create_directory 1272 | * 1273 | * Procedure that checks for and creates a local directory if possible. 1274 | */ 1275 | 1276 | static void 1277 | create_directory(char *directory) 1278 | { 1279 | struct stat local; 1280 | int create; 1281 | 1282 | create = stat(directory, &local); 1283 | 1284 | if (create == 0) { 1285 | /* If a file exists with the same name, try and remove it first. */ 1286 | 1287 | if (!S_ISDIR(local.st_mode)) { 1288 | if (remove(directory) != 0) 1289 | err(EXIT_FAILURE, "%s exists and is not a directory. Please remove it manually and restart svnup", directory); 1290 | else 1291 | create = 1; 1292 | } 1293 | } 1294 | 1295 | if (create) 1296 | if (mkdir(directory, 0755)) 1297 | err(EXIT_FAILURE, "Cannot create %s", directory); 1298 | } 1299 | 1300 | 1301 | /* concats all strings in stringlist (up to a defined margin) into a single 1302 | long string, and removes the processed entries from the list. 1303 | returns pointer to a freshly allocated string. 1304 | returns NULL if no strings are left. 1305 | if items is set to non-zero, it is used as a max value for the number 1306 | of items concatenated. 1307 | items will be filled with the number of strings joined into the result.*/ 1308 | static char *concat_stringlist(stringlist *sl, size_t maxlen, size_t *items) { 1309 | char *chain = malloc(maxlen), 1310 | *cp = chain, *ce = chain+(maxlen-1); 1311 | size_t max_items = *items; 1312 | *items = 0; 1313 | while(stringlist_getsize(sl)) { 1314 | char *s = stringlist_get(sl, 0); 1315 | size_t l = strlen(s); 1316 | if(cp + l < ce && (!max_items || *items < max_items)) { 1317 | strcpy(cp, s); 1318 | cp += l; 1319 | ++(*items); 1320 | stringlist_delete(sl, 0); 1321 | free(s); 1322 | } else { 1323 | return chain; 1324 | } 1325 | } 1326 | if(*items == 0) { free(chain); chain = 0; } 1327 | return chain; 1328 | } 1329 | 1330 | /* 1331 | * process_report_svn 1332 | * 1333 | * Procedure that sends the svn report command and saves the initial details 1334 | * in a dynamic array of file_nodes. 1335 | */ 1336 | 1337 | static void 1338 | process_report_svn(connector *connection, char *command, file_node ***file, int *file_count, int *file_max) 1339 | { 1340 | file_node *this_file; 1341 | struct tree_node *found, find; 1342 | struct stat local; 1343 | int count, path_exists, try, x; 1344 | size_t d, length, name_length, path_length, path_source_length; 1345 | char *command_start, *directory_end, *directory_start, *end; 1346 | char *item_end, *item_start, *marker, *name; 1347 | char *path_source, *start, *temp; 1348 | stringlist *buffered_commands = stringlist_new(16); 1349 | 1350 | path_source = malloc(MAXNAMLEN + 1); 1351 | 1352 | try = -1; 1353 | 1354 | retry: 1355 | 1356 | start = process_command_svn(connection, command, 0); 1357 | end = start + connection->response_length; 1358 | 1359 | command_start = command; 1360 | 1361 | directory_start = command_start; 1362 | 1363 | for (d = 0; d < connection->response_groups / 2; d++) { 1364 | if (!starts_with_lit(directory_start, "( get-dir ( ")) 1365 | errx(EXIT_FAILURE, "Error in response: %s\n", directory_start); 1366 | 1367 | directory_end = strchr(directory_start, '\n'); 1368 | 1369 | temp = strchr(directory_start, ':') + 1; 1370 | directory_start = strchr(temp, ' '); 1371 | 1372 | length = directory_start - temp; 1373 | if (length > 0) 1374 | memcpy(path_source, temp, length); 1375 | 1376 | path_source[length] = '\0'; 1377 | path_source_length = length; 1378 | 1379 | directory_start = directory_end + 1; 1380 | 1381 | /* Parse the response for file/directory names. */ 1382 | 1383 | end = connection->response + connection->response_length; 1384 | if (check_command_success(connection->protocol, &start, &end)) { 1385 | if (++try > 5) 1386 | errx(EXIT_FAILURE, "Error in svn stream. Quitting."); 1387 | 1388 | if (try > 1) 1389 | fprintf(stderr, "Error in svn stream, retry #%d\n", try); 1390 | 1391 | goto retry; 1392 | } 1393 | 1394 | parse_response_group(connection, &start, &end); 1395 | 1396 | item_start = start; 1397 | item_end = end; 1398 | 1399 | count = 0; 1400 | 1401 | while (parse_response_item(connection, end, &count, &item_start, &item_end)) { 1402 | temp = NULL; 1403 | 1404 | /* Keep track of the remote files. */ 1405 | 1406 | length = strtol(item_start + 1, (char **)NULL, 10); 1407 | if (length > MAXNAMLEN) 1408 | errx(EXIT_FAILURE, "entry_is_file file name is too long"); 1409 | 1410 | marker = strchr(item_start, ':') + 1 + length; 1411 | 1412 | if (starts_with_lit(marker, " file ")) { 1413 | this_file = new_file_node(file, file_count, file_max); 1414 | 1415 | name_length = strtol(item_start + 1, (char **)NULL, 10); 1416 | if (name_length > MAXNAMLEN) 1417 | errx(EXIT_FAILURE, "process_file_entry file name is too long"); 1418 | 1419 | name = item_start = strchr(item_start, ':') + 1; 1420 | 1421 | item_start += name_length; 1422 | *item_start = '\0'; 1423 | path_length = strlen(path_source) + name_length + 2; 1424 | 1425 | if (!starts_with_lit(item_start + 1, "file ")) 1426 | errx(EXIT_FAILURE, "process_file_entry malformed response"); 1427 | 1428 | if ((this_file->path = (char *)malloc(path_length)) == NULL) 1429 | err(EXIT_FAILURE, "process_file_entry file->path malloc"); 1430 | 1431 | snprintf(this_file->path, path_length, "%s/%s", path_source, name); 1432 | 1433 | item_start = strchr(item_start + 1, ' '); 1434 | this_file->size = strtol(item_start, (char **)NULL, 10); 1435 | } 1436 | 1437 | if (starts_with_lit(marker, " dir ")) { 1438 | length = strtol(item_start + 1, (char **)NULL, 10); 1439 | if (length > MAXNAMLEN) 1440 | errx(EXIT_FAILURE, "process_file file name is too long"); 1441 | 1442 | name = strchr(item_start, ':') + 1; 1443 | name[length] = '\0'; 1444 | 1445 | char *temp_path = malloc(BUFFER_UNIT+1); 1446 | 1447 | snprintf(temp_path, 1448 | BUFFER_UNIT, 1449 | "%s%s/%s", 1450 | connection->path_target, 1451 | path_source, 1452 | name); 1453 | 1454 | /* Create the directory locally if it doesn't exist. */ 1455 | 1456 | path_exists = stat(temp_path, &local); 1457 | 1458 | if ((path_exists != -1) && (!S_ISDIR(local.st_mode))) 1459 | errx(EXIT_FAILURE, "%s exists locally and is not a directory. Please remove it manually and restart svnup", temp_path); 1460 | 1461 | if (path_exists == -1) { 1462 | if (connection->verbosity) 1463 | printf(" + %s\n", temp_path); 1464 | 1465 | if (mkdir(temp_path, 0755) && errno != EEXIST) 1466 | err(EXIT_FAILURE, "Cannot create target directory"); 1467 | } 1468 | 1469 | /* Remove the directory from the local directory tree to avoid later attempts at pruning. */ 1470 | 1471 | find.path = temp_path; 1472 | 1473 | if ((found = RB_FIND(tree_local_directories, &local_directories, &find)) != NULL) 1474 | tree_node_free(RB_REMOVE(tree_local_directories, &local_directories, found)); 1475 | 1476 | /* Add a get-dir command to the command buffer. */ 1477 | 1478 | length += path_source_length + 1; 1479 | 1480 | char* next_command = malloc(BUFFER_UNIT+1); 1481 | snprintf(next_command, 1482 | BUFFER_UNIT, 1483 | "( get-dir ( %zd:%s/%s ( %d ) false true ( kind size ) false ) )\n", 1484 | length, 1485 | path_source, 1486 | name, 1487 | connection->revision); 1488 | 1489 | stringlist_add_dup(buffered_commands, next_command); 1490 | free(next_command); 1491 | free(temp_path); 1492 | } 1493 | 1494 | item_start = item_end + 1; 1495 | } 1496 | 1497 | start = end + 1; 1498 | } 1499 | 1500 | /* Recursively process the command buffers. */ 1501 | /* we pack up to 32KB worth of file requests into a single buffer */ 1502 | char *chain; 1503 | size_t chain_count = 0; 1504 | while((chain = concat_stringlist(buffered_commands, BUFFER_UNIT, &chain_count))) { 1505 | connection->response_groups = 2 * chain_count; 1506 | process_report_svn(connection, chain, file, file_count, file_max); 1507 | free(chain); 1508 | } 1509 | stringlist_free(buffered_commands); 1510 | free(path_source); 1511 | } 1512 | 1513 | static char* craft_http_packet(const char *host, const char* url, 1514 | const char* verb, const char* footer, char* command) { 1515 | snprintf(command, 1516 | COMMAND_BUFFER, 1517 | "%s %s HTTP/1.1\r\n" 1518 | "Host: %s\r\n" 1519 | "User-Agent: svnup-%s\r\n" 1520 | "Content-Type: text/xml\r\n" 1521 | "Connection: Keep-Alive\r\n" 1522 | "DAV: http://subversion.tigris.org/xmlns/dav/svn/depth\r\n" 1523 | "DAV: http://subversion.tigris.org/xmlns/dav/svn/mergeinfo\r\n" 1524 | "DAV: http://subversion.tigris.org/xmlns/dav/svn/log-revprops\r\n" 1525 | "Transfer-Encoding: chunked\r\n\r\n" 1526 | "%lx\r\n" 1527 | "%s" 1528 | "\r\n0\r\n\r\n", 1529 | verb, url, host, 1530 | SVNUP_VERSION, 1531 | strlen(footer), 1532 | footer); 1533 | return command; 1534 | } 1535 | 1536 | /* expects pointer to a string like "( 6: foobar )", puts "foobar" into saveloc (heap) 1537 | returns pointer to after ')' */ 1538 | static char* extract_svn_string_from_group(char* data, char** saveloc) { 1539 | char *p = data; 1540 | assert(*p == '('); 1541 | ++p; 1542 | assert(*p == ' '); 1543 | ++p; 1544 | assert(isdigit(*p)); 1545 | int n = atoi(p); 1546 | while(isdigit(*p))++p; 1547 | assert(*p == ':'); 1548 | ++p; 1549 | *saveloc = malloc(n+1); 1550 | memcpy(*saveloc, p, n); 1551 | (*saveloc)[n] = 0; 1552 | p += n + 1; 1553 | assert(*p == ')'); 1554 | return ++p; 1555 | } 1556 | 1557 | 1558 | static void process_log_svn(connector *connection) { 1559 | char command[COMMAND_BUFFER + 1], *start, *end; 1560 | 1561 | snprintf(command, COMMAND_BUFFER, 1562 | "( log ( ( 0: ) ( %d ) ( %d ) false false 0 false revprops" 1563 | " ( 10:svn:author 8:svn:date 7:svn:log ) ) ) ", 1564 | connection->revision, connection->revision); 1565 | 1566 | connection->response_groups = 2; /* 3 on success, 2 on error */ 1567 | process_command_svn(connection, command, 0); 1568 | start = connection->response; 1569 | end = connection->response + connection->response_length; 1570 | 1571 | char* group2 = connection->response + strlen(connection->response) +1; 1572 | if(group2 < end && starts_with_lit(group2, "done ( failure ( ( ")) 1573 | errx(EXIT_FAILURE, "%s", group2 + LIT_LEN("done ( failure ( ( ")); 1574 | 1575 | if (check_command_success(connection->protocol, &start, &end)) 1576 | errx(EXIT_FAILURE, "couldn't get log"); 1577 | 1578 | char buf[32], *p, *date; 1579 | snprintf(buf, sizeof buf, " %d ( ", connection->revision); 1580 | p = strstr(start, buf); 1581 | if(!p) return; 1582 | 1583 | p += strlen(buf)-2; 1584 | 1585 | p = extract_svn_string_from_group(p, &connection->commit_author); 1586 | assert(*p == ' '); 1587 | ++p; 1588 | 1589 | p = extract_svn_string_from_group(p, &connection->commit_date); 1590 | assert(*p == ' '); 1591 | ++p; 1592 | 1593 | p = extract_svn_string_from_group(p, &connection->commit_msg); 1594 | assert(*p == ' '); 1595 | 1596 | sanitize_svn_date(connection->commit_date); 1597 | } 1598 | 1599 | static void process_log_http(connector *connection) { 1600 | char command[COMMAND_BUFFER + 1], footer[1024], url[512]; 1601 | 1602 | snprintf(url, sizeof url, 1603 | "%s/%d", 1604 | connection->rev_root_stub, 1605 | connection->revision 1606 | ); 1607 | snprintf(footer, sizeof footer, 1608 | "" 1609 | "%d" 1610 | "%d" 1611 | "svn:author" 1612 | "svn:date" 1613 | "svn:log" 1614 | "" 1615 | "" 1616 | "\r\n" 1617 | , 1618 | connection->revision, 1619 | connection->revision 1620 | ); 1621 | 1622 | craft_http_packet(connection->address, url, "REPORT", footer, command); 1623 | connection->response_groups = 2; 1624 | 1625 | process_command_http(connection, command); 1626 | 1627 | char *start = connection->response, 1628 | *end = start + connection->response_length, *p; 1629 | 1630 | if(check_command_success(connection->protocol, &start, &end)) 1631 | errx(EXIT_FAILURE, "couldn't get log\n%s", start); 1632 | 1633 | if((p = strstr(start, "xml version="))) start = p+10; 1634 | 1635 | connection->commit_author = parse_xml_value(start, end, "D:creator-displayname"); 1636 | connection->commit_date = parse_xml_value(start, end, "S:date"); 1637 | connection->commit_msg = parse_xml_value(start, end, "D:comment"); 1638 | if(connection->commit_date) 1639 | sanitize_svn_date(connection->commit_date); 1640 | else 1641 | fprintf(stderr, "warning: empty reply for log request\n"); 1642 | } 1643 | 1644 | /* 1645 | * process_report_http 1646 | * 1647 | * Procedure that sends the http report command and saves the initial details 1648 | * in a dynamic array of file_nodes. 1649 | */ 1650 | 1651 | static void 1652 | process_report_http(connector *connection, file_node ***file, int *file_count, int *file_max) 1653 | { 1654 | file_node *this_file; 1655 | struct tree_node *found, find; 1656 | char command[COMMAND_BUFFER + 1], *d, *end, *href, *md5, *path; 1657 | char *start, *temp, temp_buffer[BUFFER_UNIT], *value; 1658 | char footer[512]; 1659 | 1660 | connection->response_groups = 2; 1661 | 1662 | snprintf(footer, sizeof footer, 1663 | "" 1664 | "%s" 1665 | "/%s" 1666 | "%d" 1667 | "unknown" 1668 | "" 1669 | "\r\n" 1670 | , 1671 | connection->inline_props ? "yes" : "", 1672 | connection->branch, 1673 | connection->revision, 1674 | connection->revision 1675 | ); 1676 | 1677 | char url[256]; 1678 | snprintf(url, sizeof url, "/%s/!svn/me", connection->root); 1679 | 1680 | craft_http_packet(connection->address, url, "REPORT", footer, command); 1681 | 1682 | process_command_http(connection, command); 1683 | 1684 | /* Process response for subdirectories and create them locally. */ 1685 | 1686 | start = connection->response; 1687 | end = connection->response + connection->response_length; 1688 | 1689 | int has_inline_props = !!strstr(start, "inline-props=\"true\">"); 1690 | connection->inline_props = has_inline_props; 1691 | 1692 | while ((start = strstr(start, "trunk) + strlen(connection->trunk); 1696 | snprintf(temp_buffer, BUFFER_UNIT, "%s%s", connection->path_target, temp); 1697 | 1698 | /* If a file exists with the same name, try and remove it first. */ 1699 | /* 1700 | if (stat(temp_buffer, &local) == 0) 1701 | if (S_ISDIR(local.st_mode) == 0) 1702 | if (remove(temp_buffer) != 0) 1703 | err(EXIT_FAILURE, "Please remove %s manually and restart svnup", temp_buffer); 1704 | */ 1705 | if(mkdir(temp_buffer, 0755) && errno != EEXIST) 1706 | err(EXIT_FAILURE, "failed to create directory %s", temp_buffer); 1707 | free(value); 1708 | start++; 1709 | 1710 | /* Remove the directory from the local directory tree to avoid later attempts at pruning. */ 1711 | 1712 | find.path = temp_buffer; 1713 | 1714 | if ((found = RB_FIND(tree_local_directories, &local_directories, &find)) != NULL) 1715 | tree_node_free(RB_REMOVE(tree_local_directories, &local_directories, found)); 1716 | } 1717 | 1718 | start = connection->response; 1719 | 1720 | while ((start = strstr(start, ""); 1724 | if(file_end) file_end += LIT_LEN(""); 1725 | else file_end = end; 1726 | if(has_inline_props) { 1727 | temp = strstr(start, "*"); 1728 | if(temp && temp < file_end) 1729 | this_file->executable = 1; 1730 | temp = strstr(start, "*"); 1731 | if(temp && temp < file_end) 1732 | this_file->special = 1; 1733 | this_file->size = -1; 1734 | } 1735 | md5 = parse_xml_value(start, file_end, "V:md5-checksum"); 1736 | href = parse_xml_value(start, file_end, "D:href"); 1737 | if(connection->trunk[0] == 0) 1738 | temp = href; 1739 | else 1740 | temp = strstr(href, connection->trunk); 1741 | temp += strlen(connection->trunk); 1742 | path = strdup(temp); 1743 | 1744 | /* Convert any hex encoded characters in the path. */ 1745 | 1746 | d = path; 1747 | while ((d = strchr(d, '%')) != NULL) 1748 | if ((isxdigit(d[1])) && (isxdigit(d[2]))) { 1749 | d[1] = toupper(d[1]); 1750 | d[2] = toupper(d[2]); 1751 | *d = ((isalpha(d[1]) ? 10 + d[1] -'A' : d[1] - '0') << 4) + 1752 | (isalpha(d[2]) ? 10 + d[2] -'A' : d[2] - '0'); 1753 | memmove(d + 1, d + 3, strlen(path) - (d - path + 2)); 1754 | d++; 1755 | } 1756 | 1757 | this_file->href = href; 1758 | this_file->path = path; 1759 | memcpy(this_file->md5, md5, 32); 1760 | 1761 | start = file_end; 1762 | } 1763 | } 1764 | 1765 | 1766 | /* 1767 | * parse_additional_attributes 1768 | * 1769 | * Procedure that extracts md5 signature plus last author, committed date 1770 | * and committed rev and saves them for later inclusion in revision tags. 1771 | */ 1772 | 1773 | static void 1774 | parse_additional_attributes(connector *connection, char *start, char *end, file_node *file) 1775 | { 1776 | char *md5, *temp, *value; 1777 | 1778 | if (file == NULL) return; 1779 | 1780 | if (connection->protocol == SVN) { 1781 | if ((temp = strchr(start, ':')) != NULL) { 1782 | md5 = ++temp; 1783 | memcpy(file->md5, md5, 32); 1784 | 1785 | file->executable = (strstr(start, "14:svn:executable") ? 1 : 0); 1786 | file->special = (strstr(start, "11:svn:special") ? 1 : 0); 1787 | } 1788 | } else if (connection->protocol >= HTTP) { 1789 | value = parse_xml_value(start, end, "lp1:getcontentlength"); 1790 | file->size = strtol(value, (char **)NULL, 10); 1791 | free(value); 1792 | 1793 | file->executable = (strstr(start, "") ? 1 : 0); 1794 | file->special = (strstr(start, "*") ? 1 : 0); 1795 | } 1796 | } 1797 | 1798 | static int get_content_length(const char *start, const char *end, size_t *len) { 1799 | char *p = strstr(start, "Content-Length: "); 1800 | if(!p || p > end) return 0; 1801 | *len = strtol(p + LIT_LEN("Content-Length: "), NULL, 10); 1802 | return 1; 1803 | } 1804 | 1805 | /* 1806 | * get_files 1807 | * 1808 | * Procedure that extracts and saves files from the response stream. 1809 | */ 1810 | 1811 | static void 1812 | get_files(connector *connection, char *command, char *path_target, file_node **file, int file_start, int file_end) 1813 | { 1814 | int try, x, block_size, block_size_markers, file_block_remainder; 1815 | int first_response, last_response, offset, position, raw_size, saved; 1816 | char *begin, *end, file_path_target[BUFFER_UNIT], *gap, *start, *temp_end; 1817 | char md5_check[33]; 1818 | 1819 | /* Calculate the number of bytes the server is going to send back. */ 1820 | 1821 | try = 0; 1822 | retry: 1823 | if (try) reset_connection(connection); 1824 | 1825 | raw_size = 0; 1826 | 1827 | if (connection->protocol >= HTTP) { 1828 | process_command_http(connection, command); 1829 | 1830 | start = connection->response; 1831 | 1832 | for (x = file_start; x <= file_end; x++) { 1833 | if ((file[x] == NULL) || (file[x]->download == 0)) 1834 | continue; 1835 | 1836 | if(start == connection->response + connection->response_length) 1837 | goto increment_tries; 1838 | 1839 | if(!(end = strstr(start, "\r\n\r\n"))) 1840 | goto increment_tries; 1841 | 1842 | if(file[x]->size == -1LL) { 1843 | size_t ns; 1844 | if(!get_content_length(start, end, &ns)) 1845 | errx(EXIT_FAILURE, "failed to extract Content-Length!"); 1846 | file[x]->size = ns; 1847 | } 1848 | end += 4; 1849 | file[x]->raw_size = file[x]->size + (end - start); 1850 | start = end + file[x]->size; 1851 | raw_size += file[x]->raw_size; 1852 | } 1853 | } 1854 | 1855 | if (connection->protocol == SVN) { 1856 | last_response = 20; 1857 | first_response = 84; 1858 | 1859 | x = connection->revision; 1860 | while ((int)(x /= 10) > 0) 1861 | first_response++; 1862 | 1863 | for (x = file_start; x <= file_end; x++) { 1864 | if ((file[x] == NULL) || (file[x]->download == 0)) 1865 | continue; 1866 | 1867 | block_size_markers = 6 * (int)(file[x]->size / BUFFER_UNIT); 1868 | if (file[x]->size % BUFFER_UNIT) 1869 | block_size_markers += 3; 1870 | 1871 | file_block_remainder = file[x]->size % BUFFER_UNIT; 1872 | while ((int)(file_block_remainder /= 10) > 0) 1873 | block_size_markers++; 1874 | 1875 | file[x]->raw_size = file[x]->size + 1876 | first_response + 1877 | last_response + 1878 | block_size_markers; 1879 | 1880 | raw_size += file[x]->raw_size; 1881 | } 1882 | 1883 | process_command_svn(connection, command, raw_size); 1884 | } 1885 | 1886 | /* Process the response stream and extract each file. */ 1887 | 1888 | position = raw_size; 1889 | 1890 | for (x = file_end; x >= file_start; x--) { 1891 | if (file[x]->download == 0) 1892 | continue; 1893 | 1894 | char *tmp = strip_rev_root_stub(connection, file[x]->path); 1895 | 1896 | snprintf(file_path_target, 1897 | BUFFER_UNIT, 1898 | "%s%s", 1899 | path_target, 1900 | tmp); 1901 | 1902 | /* Isolate the file from the response stream. */ 1903 | 1904 | end = connection->response + position; 1905 | start = end - file[x]->raw_size; 1906 | begin = end - file[x]->size; 1907 | temp_end = end; 1908 | 1909 | if (check_command_success(connection->protocol, &start, &temp_end)) { 1910 | increment_tries:; 1911 | if (++try > 5) 1912 | errx(EXIT_FAILURE, "Error in get_files. Quitting."); 1913 | 1914 | if (try > 1) 1915 | fprintf(stderr, "Error in get files, retry #%d\n", try); 1916 | 1917 | goto retry; 1918 | } 1919 | 1920 | if (connection->protocol == SVN) { 1921 | start = find_response_end(connection->protocol, start, temp_end) + 1; 1922 | begin = strchr(start, ':') + 1; 1923 | block_size = strtol(start, (char **)NULL, 10); 1924 | offset = 0; 1925 | start = begin; 1926 | 1927 | while (block_size == BUFFER_UNIT) { 1928 | start += block_size + offset; 1929 | gap = start; 1930 | start = strchr(gap, ':') + 1; 1931 | block_size = strtol(gap, (char **)NULL, 10); 1932 | /* FIXME this memmove here was identified as writing memory 1933 | outside the allocated area (if the aread allocated was very close to 1934 | the actually transfered content size); probably by as many 1935 | bytes as it tries to move the block around, which is identical to the SVN 1936 | header for that block. */ 1937 | memmove(gap, start, file[x]->raw_size - (start - begin) + 1); 1938 | offset = gap - start; 1939 | } 1940 | } 1941 | 1942 | /* 1943 | * Check to make sure the MD5 signature of the file in the buffer 1944 | * matches what the svn server originally reported. 1945 | */ 1946 | 1947 | if (connection->verbosity > 1) 1948 | printf("\r\e[0K\r"); 1949 | 1950 | /* Make sure the MD5 checksums match before saving the file. */ 1951 | 1952 | if (strncmp(file[x]->md5, md5sum(begin, file[x]->size, md5_check), 33) != 0) { 1953 | begin[file[x]->size] = '\0'; 1954 | errx(EXIT_FAILURE, "MD5 checksum mismatch: should be %s, calculated %s\n", file[x]->md5, md5_check); 1955 | } 1956 | 1957 | saved = save_file(file_path_target, 1958 | begin, 1959 | begin + file[x]->size, 1960 | file[x]->executable, 1961 | file[x]->special); 1962 | 1963 | if ((saved) && (connection->verbosity)) 1964 | printf(" + %s\n", file_path_target); 1965 | 1966 | position -= file[x]->raw_size; 1967 | bzero(connection->response + position, file[x]->raw_size); 1968 | } 1969 | } 1970 | 1971 | 1972 | /* 1973 | * progress_indicator 1974 | * 1975 | * Procedure that neatly prints the current file and its position in the file list as a percentage. 1976 | */ 1977 | 1978 | static void 1979 | progress_indicator(connector *connection, char *path, int f, int file_count) 1980 | { 1981 | struct winsize window; 1982 | int file_width, term_width, x; 1983 | char *columns, file_path_target[BUFFER_UNIT], temp_buffer[BUFFER_UNIT]; 1984 | 1985 | term_width = -1; 1986 | file_width = 2; 1987 | 1988 | x = file_count; 1989 | while ((int)(x /= 10) > 0) 1990 | file_width++; 1991 | 1992 | /* Figure out the width of the terminal window. */ 1993 | 1994 | if (isatty(STDERR_FILENO)) { 1995 | if (((columns = getenv("COLUMNS")) != NULL) && (*columns != '\0')) 1996 | term_width = strtol(columns, (char **)NULL, 10); 1997 | else { 1998 | if ((ioctl(STDERR_FILENO, TIOCGWINSZ, &window) != -1) && (window.ws_col > 0)) 1999 | term_width = window.ws_col; 2000 | } 2001 | } 2002 | 2003 | snprintf(file_path_target, 2004 | BUFFER_UNIT, 2005 | "%s%s", 2006 | connection->path_target, 2007 | path); 2008 | 2009 | /* If the width of the window is smaller than the output line, trim 2010 | * off the beginning of the path. */ 2011 | 2012 | x = (term_width == -1) ? 1 : 0; 2013 | if (15 + 2 * file_width + strlen(file_path_target) < (unsigned int)term_width) 2014 | x = 1; 2015 | 2016 | snprintf(temp_buffer, 2017 | BUFFER_UNIT, 2018 | "% *d of %d (% 5.1f%%) %s%s\e[0K\r", 2019 | file_width, 2020 | f + 1, 2021 | file_count, 2022 | 100.0 * f / (double)file_count, 2023 | (x ? "" : "..."), 2024 | file_path_target + (x ? 0 : strlen(file_path_target) - term_width + file_width + file_width + 18)); 2025 | 2026 | fprintf(stderr, "%s", temp_buffer); 2027 | } 2028 | 2029 | static void 2030 | usage_svn(char *arg0) { 2031 | fprintf(stderr, 2032 | "svn-lite version %s by John Mehr & rofl0r\n\n" 2033 | "Usage: svn command [options] [args]\n\n" 2034 | "commands:\n\n" 2035 | "info [options] TARGET\n" 2036 | " print some information about TARGET.\n" 2037 | " TARGET may either be an URL or a local directory.\n\n" 2038 | "log [options] TARGET\n" 2039 | " print commit log of TARGET\n" 2040 | " TARGET may either be an URL or a local directory.\n\n" 2041 | "checkout/co [options] URL [PATH]\n" 2042 | " checkout repository (equivalent to git clone/git pull).\n" 2043 | " if PATH is omitted, basename of URL will be used as destination\n" 2044 | "\n" 2045 | "options applicable to all commands:\n" 2046 | " -r or --revision NUMBER (default: 0)\n" 2047 | " -v or --verbosity NUMBER (default: 1)\n" 2048 | , SVNUP_VERSION 2049 | ); 2050 | exit(EXIT_FAILURE); 2051 | } 2052 | 2053 | static int has_revision_option(enum svn_job mode) { 2054 | switch(mode) { 2055 | case SVN_INFO: case SVN_CO: case SVN_LOG: 2056 | return 1; 2057 | } 2058 | return 0; 2059 | } 2060 | 2061 | static char *protocol_check(char *str, connector *connection) 2062 | { 2063 | char *p = strchr(str, ':'); 2064 | if(!p || p[1] != '/' || p[2] != '/') { 2065 | connection->protocol = NONE; 2066 | return str; 2067 | } 2068 | if(!protocol_from_str(str, connection)) { 2069 | errx(EXIT_FAILURE, "unknown protocol %s\n", str); 2070 | } 2071 | p += 3; 2072 | return p; 2073 | } 2074 | 2075 | 2076 | static void 2077 | getopts_svn(int argc, char **argv, connector *connection) 2078 | { 2079 | int a = 1; 2080 | 2081 | if(argc < 2) usage_svn(argv[0]); 2082 | if(!strcmp(argv[a], "checkout") || !strcmp(argv[a], "co")) 2083 | connection->job = SVN_CO; 2084 | else if(!strcmp(argv[a], "info")) 2085 | connection->job = SVN_INFO; 2086 | else if(!strcmp(argv[a], "log")) 2087 | connection->job = SVN_LOG; 2088 | else 2089 | usage_svn(argv[0]); 2090 | ++a; 2091 | while(1) { 2092 | int opt = 0; 2093 | if(!strcmp(argv[a], "-r") || !strcmp(argv[a], "--revision")) 2094 | opt = 1; 2095 | else if(!strcmp(argv[a], "-v") || !strcmp(argv[a], "--verbosity")) 2096 | opt = 2; 2097 | if(!opt) break; 2098 | if(opt == 1 && !has_revision_option(connection->job)) 2099 | usage_svn(argv[0]); 2100 | ++a; 2101 | if(a >= argc) usage_svn(argv[0]); 2102 | int n = atoi(argv[a++]); 2103 | if(opt == 1) connection->revision = n; 2104 | else if(opt == 2) connection->verbosity = n; 2105 | if(a >= argc) usage_svn(argv[0]); 2106 | } 2107 | 2108 | char *p = protocol_check(argv[a], connection), *q, *dst; 2109 | if(connection->job == SVN_CO && connection->protocol == NONE) 2110 | usage_svn(argv[0]); 2111 | if(connection->protocol != NONE) { 2112 | if((q = strchr(p, ':'))) { 2113 | connection->port = atoi(q+1); 2114 | *q = 0; 2115 | connection->address = strdup(p); 2116 | *q = ':'; 2117 | } else if((q = strchr(p, '/'))) { 2118 | *q = 0; 2119 | connection->address = strdup(p); 2120 | *q = '/'; 2121 | } 2122 | if(q && *q == ':') q = strchr(q, '/'); 2123 | if(!q) { 2124 | err(EXIT_FAILURE, "expected '/' in URL!"); 2125 | } 2126 | p = ++q; 2127 | connection->branch = strdup(p); 2128 | if(connection->job == SVN_CO) { 2129 | if(++a >= argc) 2130 | dst = basename(connection->branch); 2131 | else 2132 | dst = argv[a]; 2133 | connection->path_target = strdup(dst); 2134 | } 2135 | } else { 2136 | connection->path_target = strdup(argv[a]); 2137 | } 2138 | if(++a < argc) usage_svn(argv[0]); 2139 | 2140 | if(connection->path_target) { 2141 | char buf[PATH_MAX]; 2142 | snprintf(buf, sizeof buf, "%s/.svnup", connection->path_target); 2143 | connection->path_work = strdup(buf); 2144 | } 2145 | 2146 | connection->trim_tree = 1; 2147 | } 2148 | 2149 | static void write_info_or_log(connector *connection) { 2150 | if(connection->job == SVN_LOG) { 2151 | char deco[73]; 2152 | memset(deco, '-', 72); 2153 | deco[72] = 0; 2154 | fprintf(stdout, "%s\n", deco); 2155 | /* some broken svn repos have empty revisions, and svn log prints only a 2156 | single line of decorations, e.g. 2157 | 2158 | user@~$ svn log -r 70 svn://repo.hu/genht/trunk 2159 | ------------------------------------------------------------------------ 2160 | user@~$ 2161 | 2162 | in case of svn info, the last "good" rev is displayed as "Last Changed Rev" 2163 | 2164 | user@~$ svn info -r 70 svn://repo.hu/genht/trunk 2165 | Path: trunk 2166 | URL: svn://repo.hu/genht/trunk 2167 | Relative URL: ^/trunk 2168 | Repository Root: svn://repo.hu/genht 2169 | Repository UUID: 050b73db-cdb7-47b8-9107-1ae054b27eea 2170 | Revision: 70 2171 | Node Kind: directory 2172 | Last Changed Author: igor2 2173 | Last Changed Rev: 69 2174 | Last Changed Date: 2017-06-27 07:06:39 +0000 (Tue, 27 Jun 2017) 2175 | user@~$ 2176 | */ 2177 | if(connection->commit_author) { 2178 | fprintf(stdout, "r%u | %s | %s |\n\n", connection->revision, connection->commit_author, connection->commit_date); 2179 | fprintf(stdout, "%s\n%s\n", connection->commit_msg, deco); 2180 | } 2181 | } else if(connection->job == SVN_INFO) { 2182 | fprintf(stdout, "Revision: %u\n", connection->revision); 2183 | if(connection->commit_author) { 2184 | fprintf(stdout, "Last Changed Author: %s\n", connection->commit_author); 2185 | fprintf(stdout, "Last Changed Rev: %u\n", connection->revision); 2186 | fprintf(stdout, "Last Changed Date: %s +0000\n", connection->commit_date); 2187 | } 2188 | } else { 2189 | assert(0); 2190 | } 2191 | } 2192 | 2193 | static const char* protocol_to_string(int proto) { 2194 | static const char proto_strmap[][6] = { 2195 | [SVN] = "svn", [HTTP] = "http", [HTTPS] = "https", 2196 | }; 2197 | return proto >= SVN && proto <= HTTPS ? proto_strmap[proto] : NULL; 2198 | } 2199 | 2200 | static void save_revision_file(connector *connection, char *svn_version_path) { 2201 | FILE *f; 2202 | if (!(f = fopen(svn_version_path, "w"))) 2203 | err(EXIT_FAILURE, "write file failure %s", svn_version_path); 2204 | const char *ps = protocol_to_string(connection->protocol); 2205 | fprintf(f, "rev=%u\n", connection->revision); 2206 | fprintf(f, "url=%s://%s/%s\n", ps, connection->address, connection->branch); 2207 | fprintf(f, "date=%s\n", connection->commit_date ? connection->commit_date : ""); 2208 | fprintf(f, "author=%s\n", connection->commit_author ? connection->commit_author : ""); 2209 | fprintf(f, "log=%s\n", connection->commit_msg ? connection->commit_msg : ""); 2210 | fclose(f); 2211 | chmod(svn_version_path, 0644); 2212 | } 2213 | 2214 | static void read_revision_file(connector *connection, char *svn_version_path) { 2215 | FILE *f = fopen(svn_version_path, "r"); 2216 | char buf[1024]; 2217 | int in_log = 0; 2218 | if(!f) errx(EXIT_FAILURE, "couldn't open %s", svn_version_path); 2219 | while(fgets(buf, sizeof buf, f)) { 2220 | if(in_log) { 2221 | size_t l = strlen(connection->commit_msg); 2222 | connection->commit_msg = realloc(connection->commit_msg, l + 1 + sizeof buf); 2223 | if(l && connection->commit_msg[l-1] == '\n'); 2224 | else { connection->commit_msg[l] = '\n'; ++l; } 2225 | char *p = strchr(buf, '\n'); 2226 | if(p) *p = 0; 2227 | memcpy(connection->commit_msg+l, buf, strlen(buf)+1); 2228 | } else if(!strncmp(buf, "rev=", 4)) { 2229 | unsigned rev = atoi(buf+4); 2230 | if(connection->revision && connection->revision != rev) 2231 | errx(EXIT_FAILURE, "no local date for selected revision available, got %u", rev); 2232 | connection->revision = rev; 2233 | } else if(!strncmp(buf, "date=", 5)) { 2234 | char *p = strchr(buf+5, '\n'); 2235 | if(!p) errx(EXIT_FAILURE, "malformed file %s", svn_version_path); 2236 | *p = 0; 2237 | p = buf + 5; 2238 | if(*p) 2239 | connection->commit_date = strdup(p); 2240 | } else if(!strncmp(buf, "author=", 7)) { 2241 | char *p = strchr(buf+7, '\n'); 2242 | if(!p) errx(EXIT_FAILURE, "malformed file %s", svn_version_path); 2243 | *p = 0; 2244 | p = buf+7; 2245 | if(*p) 2246 | connection->commit_author = strdup(p); 2247 | } else if(!strncmp(buf, "log=", 4)) { 2248 | /* log entry may span multiple lines, therefore is last in file */ 2249 | char *p = strchr(buf+4, '\n'); 2250 | if(p) *p = 0; 2251 | connection->commit_msg = malloc(strlen(buf+4)+1); 2252 | memcpy(connection->commit_msg, buf+4, strlen(buf+4)+1); 2253 | in_log = 1; 2254 | } 2255 | } 2256 | fclose(f); 2257 | } 2258 | 2259 | static void load_known_files(connector *connection) { 2260 | struct stat local; 2261 | int fd; 2262 | char *md5, *value, *path; 2263 | size_t length; 2264 | struct tree_node *data; 2265 | 2266 | length = strlen(connection->path_work) + MAXNAMLEN; 2267 | 2268 | connection->known_files_old = (char *)malloc(length); 2269 | connection->known_files_new = (char *)malloc(length); 2270 | 2271 | snprintf(connection->known_files_old, length, "%s/known_files", connection->path_work); 2272 | snprintf(connection->known_files_new, length, "%s/known_files.new", connection->path_work); 2273 | 2274 | if (stat(connection->known_files_old, &local) != -1) { 2275 | connection->known_files_size = local.st_size; 2276 | 2277 | if ((connection->known_files = (char *)malloc(connection->known_files_size + 1)) == NULL) 2278 | err(EXIT_FAILURE, "connection.known_files malloc"); 2279 | 2280 | if ((fd = open(connection->known_files_old, O_RDONLY)) == -1) 2281 | err(EXIT_FAILURE, "open file (%s)", connection->known_files_old); 2282 | 2283 | if (read(fd, connection->known_files, connection->known_files_size) != connection->known_files_size) 2284 | err(EXIT_FAILURE, "read file error (%s)", connection->known_files_old); 2285 | 2286 | connection->known_files[connection->known_files_size] = '\0'; 2287 | close(fd); 2288 | value = connection->known_files; 2289 | 2290 | while (*value) { 2291 | md5 = value; 2292 | path = strchr(value, '\t') + 1; 2293 | value = strchr(path, '\n'); 2294 | *value++ = '\0'; 2295 | md5[32] = '\0'; 2296 | data = (struct tree_node *)malloc(sizeof(struct tree_node)); 2297 | data->path = strdup(path); 2298 | data->md5 = strdup(md5); 2299 | RB_INSERT(tree_known_files, &known_files, data); 2300 | } 2301 | } 2302 | } 2303 | 2304 | /* 2305 | * main 2306 | * 2307 | * A lightweight, dependency-free program to pull source from an Apache Subversion server. 2308 | */ 2309 | 2310 | int 2311 | main(int argc, char **argv) 2312 | { 2313 | connector connection = { 2314 | .response_blocks = 16, 2315 | .verbosity = 1, 2316 | .family = AF_UNSPEC, 2317 | .protocol = HTTPS, 2318 | .socket_descriptor = -1, 2319 | }; 2320 | 2321 | struct tree_node *data, *found, *next; 2322 | file_node **file; 2323 | 2324 | char command[COMMAND_BUFFER + 1], *end; 2325 | char *md5, *path, *start, temp_buffer[BUFFER_UNIT], *value; 2326 | char svn_version_path[255]; 2327 | int b; 2328 | int c, command_count; 2329 | int f, f0, fd, file_count, file_max, length; 2330 | 2331 | file = NULL; 2332 | 2333 | file_count = command_count = 0; 2334 | f = f0 = length = 0; 2335 | 2336 | file_max = BUFFER_UNIT; 2337 | 2338 | if ((file = (file_node **)malloc(file_max * sizeof(file_node **))) == NULL) 2339 | err(EXIT_FAILURE, "process_directory source malloc"); 2340 | 2341 | command[0] = '\0'; 2342 | 2343 | getopts_svn(argc, argv, &connection); 2344 | 2345 | /* Create the destination directories if they doesn't exist. */ 2346 | 2347 | if(connection.path_target) create_directory(connection.path_target); 2348 | if(connection.path_work) { 2349 | create_directory(connection.path_work); 2350 | snprintf(svn_version_path, sizeof(svn_version_path), 2351 | "%s/revision", connection.path_work); 2352 | } else svn_version_path[0] = 0; 2353 | 2354 | if(connection.protocol == NONE) { 2355 | read_revision_file(&connection, svn_version_path); 2356 | write_info_or_log(&connection); 2357 | return 0; 2358 | } 2359 | 2360 | 2361 | /* Load the list of known files and MD5 signatures, if they exist. */ 2362 | 2363 | if(connection.path_work) { 2364 | load_known_files(&connection); 2365 | 2366 | if ((connection.extra_files) || (connection.trim_tree)) 2367 | find_local_files_and_directories(connection.path_target, "", 1); 2368 | else 2369 | find_local_files_and_directories(connection.path_target, "", 0); 2370 | } 2371 | 2372 | /* Initialize connection with the server and get the latest revision number. */ 2373 | 2374 | if ((connection.response = (char *)malloc(connection.response_blocks * BUFFER_UNIT + 1)) == NULL) 2375 | err(EXIT_FAILURE, "main connection.response malloc"); 2376 | 2377 | reset_connection(&connection); 2378 | 2379 | /* Send initial response string. */ 2380 | 2381 | if (connection.protocol == SVN) { 2382 | connection.response_groups = 1; 2383 | process_command_svn(&connection, "", 0); 2384 | 2385 | snprintf(command, 2386 | COMMAND_BUFFER, 2387 | "( 2 ( edit-pipeline svndiff1 absent-entries commit-revprops depth log-revprops atomic-revprops partial-replay ) %ld:svn://%s/%s %ld:svnup-%s ( ) )\n", 2388 | strlen(connection.address) + strlen(connection.branch) + 7, 2389 | connection.address, 2390 | connection.branch, 2391 | strlen(SVNUP_VERSION) + 6, 2392 | SVNUP_VERSION); 2393 | 2394 | process_command_svn(&connection, command, 0); 2395 | 2396 | start = connection.response; 2397 | end = connection.response + connection.response_length; 2398 | if (check_command_success(connection.protocol, &start, &end)) 2399 | exit(EXIT_FAILURE); 2400 | 2401 | /* Login anonymously. */ 2402 | 2403 | connection.response_groups = 2; 2404 | process_command_svn(&connection, "( ANONYMOUS ( 0: ) )\n", 0); 2405 | 2406 | /* Get latest revision number. */ 2407 | 2408 | if (connection.revision <= 0) { 2409 | process_command_svn(&connection, "( get-latest-rev ( ) )\n", 0); 2410 | 2411 | start = connection.response; 2412 | end = connection.response + connection.response_length; 2413 | if (check_command_success(connection.protocol, &start, &end)) 2414 | exit(EXIT_FAILURE); 2415 | 2416 | if ((start != NULL) && starts_with_lit(start, "( success ( ")) { 2417 | start += LIT_LEN("( success ( "); 2418 | value = start; 2419 | while (*start != ' ') start++; 2420 | *start = '\0'; 2421 | 2422 | connection.revision = strtol(value, (char **)NULL, 10); 2423 | } else errx(EXIT_FAILURE, "Cannot retrieve latest revision."); 2424 | } 2425 | 2426 | /* Check to make sure client-supplied remote path is a directory. */ 2427 | 2428 | snprintf(command, 2429 | COMMAND_BUFFER, 2430 | "( check-path ( 0: ( %d ) ) )\n", 2431 | connection.revision); 2432 | process_command_svn(&connection, command, 0); 2433 | 2434 | if ((strcmp(connection.response, "( success ( ( ) 0: ) )") != 0) && 2435 | (strcmp(connection.response + 23, "( success ( dir ) ) ") != 0)) 2436 | errx(EXIT_FAILURE, 2437 | "Remote path %s is not a repository directory.\n%s", 2438 | connection.branch, 2439 | connection.response); 2440 | 2441 | process_log_svn(&connection); 2442 | } 2443 | 2444 | else if (connection.protocol >= HTTP) { 2445 | char url[512]; 2446 | static const char *footer = 2447 | "" 2448 | "" 2449 | "" 2450 | "\r\n"; 2451 | 2452 | snprintf(url, sizeof url, "/%s", connection.branch); 2453 | craft_http_packet(connection.address, url, "OPTIONS", footer, command); 2454 | connection.response_groups = 2; 2455 | process_command_http(&connection, command); 2456 | 2457 | /* Get the latest revision number. */ 2458 | 2459 | if (connection.revision <= 0) { 2460 | if ((value = strstr(connection.response, "SVN-Youngest-Rev: ")) == NULL) 2461 | errx(EXIT_FAILURE, "Cannot find revision number."); 2462 | else 2463 | connection.revision = strtol(value + 18, (char **)NULL, 10); 2464 | } 2465 | 2466 | char buf[1024]; 2467 | if(!http_extract_header_value(connection.response, "SVN-Repository-Root", buf, sizeof buf)) { 2468 | errx(EXIT_FAILURE, "Cannot find SVN Repository Root."); 2469 | } 2470 | assert(buf[0] == '/'); 2471 | connection.root = strdup(buf + 1 /* skip leading '/' */); 2472 | if ((path = strstr(connection.branch, connection.root))) { 2473 | if(strlen(connection.branch) == strlen(connection.root)) 2474 | path = ""; 2475 | else 2476 | path += strlen(connection.root) + 1; 2477 | } 2478 | else errx(EXIT_FAILURE, "Cannot find SVN Repository Trunk."); 2479 | 2480 | connection.trunk = strdup(path); 2481 | 2482 | if(http_extract_header_value(connection.response, "SVN-Rev-Root-Stub", buf, sizeof buf)) { 2483 | assert(buf[0] == '/'); 2484 | connection.rev_root_stub = strdup(buf); 2485 | } 2486 | 2487 | if(connection.rev_root_stub) process_log_http(&connection); 2488 | } 2489 | 2490 | if (connection.job == SVN_LOG || connection.job == SVN_INFO) { 2491 | write_info_or_log(&connection); 2492 | return 0; 2493 | } 2494 | 2495 | if (connection.verbosity) 2496 | printf("# Revision: %d\n", connection.revision); 2497 | 2498 | if (connection.verbosity > 1) { 2499 | fprintf(stderr, "# Protocol: %s\n", protocol_to_string(connection.protocol)); 2500 | fprintf(stderr, "# Address: %s\n", connection.address); 2501 | fprintf(stderr, "# Port: %d\n", connection.port); 2502 | fprintf(stderr, "# Branch: %s\n", connection.branch); 2503 | fprintf(stderr, "# Target: %s\n", connection.path_target); 2504 | fprintf(stderr, "# Trim tree: %s\n", connection.trim_tree ? "Yes" : "No"); 2505 | fprintf(stderr, "# Show extra files: %s\n", connection.extra_files ? "Yes" : "No"); 2506 | fprintf(stderr, "# Known files directory: %s\n", connection.path_work); 2507 | } 2508 | 2509 | /* at this point, we're checking out a revision, so we request report(s) containing 2510 | the names of all files and dirs in that revision, including some additional 2511 | properties that vary among protocol and features of the server */ 2512 | 2513 | if (connection.protocol == SVN) { 2514 | connection.response_groups = 2; 2515 | 2516 | snprintf(command, 2517 | COMMAND_BUFFER, 2518 | "( get-dir ( 0: ( %d ) false true ( kind size ) false ) )\n", 2519 | connection.revision); 2520 | 2521 | process_report_svn(&connection, command, &file, &file_count, &file_max); 2522 | } 2523 | 2524 | if (connection.protocol >= HTTP) { 2525 | process_report_http(&connection, &file, &file_count, &file_max); 2526 | 2527 | start = connection.response; 2528 | end = connection.response + connection.response_length; 2529 | if (check_command_success(connection.protocol, &start, &end)) 2530 | exit(EXIT_FAILURE); 2531 | } 2532 | 2533 | /* if we have received the md5 checksum already, filter out the files that 2534 | exist locally and have a matching checksum, so we don't need to download them, 2535 | nor request additional properties about them. */ 2536 | for (f = 0; f < file_count; ++f) { 2537 | check_md5(&connection, file[f]); 2538 | } 2539 | 2540 | /* Get additional file information not contained in the first report and store the 2541 | commands in a list. */ 2542 | 2543 | stringlist *buffered_commands = stringlist_new(32); 2544 | 2545 | /* only retrieve additional information about files 2546 | if we haven't received inline props already */ 2547 | if (!connection.inline_props) 2548 | for (f = 0; f < file_count; f++) { 2549 | temp_buffer[0] = '\0'; 2550 | 2551 | if (connection.protocol == SVN) 2552 | snprintf(temp_buffer, 2553 | BUFFER_UNIT, 2554 | "( get-file ( %zd:%s ( %d ) true false false ) )\n", 2555 | strlen(file[f]->path), 2556 | file[f]->path, 2557 | connection.revision); 2558 | 2559 | if (connection.protocol >= HTTP) { 2560 | if (file[f]->download) { 2561 | snprintf(temp_buffer, 2562 | BUFFER_UNIT, 2563 | "PROPFIND %s HTTP/1.1\r\n" 2564 | "Depth: 1\r\n" 2565 | "Host: %s\r\n\r\n", 2566 | file[f]->href, 2567 | connection.address); 2568 | } 2569 | } 2570 | 2571 | if (temp_buffer[0] != '\0') { 2572 | stringlist_add_dup(buffered_commands, temp_buffer); 2573 | } 2574 | } 2575 | 2576 | /* Process the additional commands to retrieve extended attributes. 2577 | In case of SVN, this includes the md5 checksum and special(i.e. symlink) 2578 | and executable properties, for HTTP only the latter 2 plus filesize. */ 2579 | 2580 | #define MAX_HTTP_REQUESTS_PER_PACKET 95 2581 | 2582 | char *chain; 2583 | size_t chain_count = connection.protocol >= HTTP ? MAX_HTTP_REQUESTS_PER_PACKET : 0; 2584 | f = f0 = 0; 2585 | while ((chain = concat_stringlist(buffered_commands, BUFFER_UNIT, &chain_count))) { 2586 | size_t chain_items = chain_count; 2587 | chain_count = connection.protocol >= HTTP ? MAX_HTTP_REQUESTS_PER_PACKET : 0; 2588 | connection.response_groups = chain_items * 2; 2589 | 2590 | if (connection.protocol >= HTTP) 2591 | process_command_http(&connection, chain); 2592 | 2593 | if (connection.protocol == SVN) 2594 | process_command_svn(&connection, chain, 0); 2595 | 2596 | free(chain); 2597 | 2598 | start = connection.response; 2599 | end = start + connection.response_length; 2600 | 2601 | command[0] = '\0'; 2602 | connection.response_groups = 0; 2603 | 2604 | for (length = 0, c = 0; c < chain_items; c++) { 2605 | if (connection.protocol >= HTTP) 2606 | while (f < file_count && file[f]->download == 0) { 2607 | /* on http, skip files that already had their md5 checked, 2608 | therefore no PROPFIND request was submitted, 2609 | so they're not in the chain */ 2610 | if (connection.verbosity > 1) 2611 | progress_indicator(&connection, file[f]->path, f, file_count); 2612 | 2613 | f++; 2614 | } 2615 | 2616 | if (check_command_success(connection.protocol, &start, &end)) 2617 | exit(EXIT_FAILURE); 2618 | 2619 | if (connection.protocol >= HTTP) 2620 | parse_response_group(&connection, &start, &end); 2621 | 2622 | if (connection.protocol == SVN) 2623 | end = strchr(start, '\0'); 2624 | 2625 | parse_additional_attributes(&connection, start, end, file[f]); 2626 | 2627 | if (connection.verbosity > 1) 2628 | progress_indicator(&connection, file[f]->path, f, file_count); 2629 | 2630 | start = end + 1; 2631 | f++; 2632 | } 2633 | } 2634 | stringlist_free(buffered_commands); 2635 | 2636 | /* check md5 again for those still unchecked; in case we only retrieved 2637 | the checked-in file's checksum right now via additional attributes. */ 2638 | for (f = 0; f < file_count; ++f) { 2639 | check_md5(&connection, file[f]); 2640 | } 2641 | 2642 | buffered_commands = stringlist_new(64); 2643 | 2644 | for (f=0; f < file_count; ++f) { 2645 | if (file[f]->download) { 2646 | if (connection.protocol >= HTTP) 2647 | snprintf(temp_buffer, 2648 | BUFFER_UNIT, 2649 | "GET %s HTTP/1.1\r\n" 2650 | "Host: %s\r\n" 2651 | "Connection: Keep-Alive\r\n\r\n", 2652 | file[f]->href, 2653 | connection.address); 2654 | 2655 | if (connection.protocol == SVN) 2656 | snprintf(temp_buffer, 2657 | BUFFER_UNIT, 2658 | "( get-file ( %zd:%s ( %d ) false true false ) )\n", 2659 | strlen(file[f]->path), 2660 | file[f]->path, 2661 | connection.revision); 2662 | 2663 | stringlist_add_dup(buffered_commands, temp_buffer); 2664 | } 2665 | } 2666 | 2667 | /* download the actual files missing from tree */ 2668 | chain_count = connection.protocol >= HTTP ? MAX_HTTP_REQUESTS_PER_PACKET : 0; 2669 | f = f0 = 0; 2670 | while ((chain = concat_stringlist(buffered_commands, BUFFER_UNIT, &chain_count))) { 2671 | size_t chain_items = chain_count; 2672 | size_t file_incs = 0; 2673 | chain_count = connection.protocol >= HTTP ? MAX_HTTP_REQUESTS_PER_PACKET : 0; 2674 | connection.response_groups = chain_items * 2; 2675 | 2676 | while (f < file_count && file_incs < chain_items) { 2677 | if(file[f]->download != 0) ++file_incs; 2678 | ++f; 2679 | } 2680 | get_files(&connection, chain, connection.path_target, 2681 | file, f0, f - 1); 2682 | 2683 | if ((connection.verbosity > 1) && (f < file_count)) 2684 | progress_indicator(&connection, file[f]->path, f, file_count); 2685 | 2686 | f0 = f; 2687 | free(chain); 2688 | } 2689 | stringlist_free(buffered_commands); 2690 | 2691 | save_known_file_list(&connection, file, file_count); 2692 | 2693 | /* Save details about the current revision */ 2694 | save_revision_file(&connection, svn_version_path); 2695 | 2696 | /* Any files left in the tree are safe to delete. */ 2697 | 2698 | for (data = RB_MIN(tree_known_files, &known_files); data != NULL; data = next) { 2699 | next = RB_NEXT(tree_known_files, head, data); 2700 | 2701 | if ((found = RB_FIND(tree_local_files, &local_files, data)) != NULL) 2702 | tree_node_free(RB_REMOVE(tree_local_files, &local_files, found)); 2703 | 2704 | if (strncmp(connection.path_work, data->path, strlen(connection.path_work))) 2705 | prune(&connection, data->path); 2706 | 2707 | tree_node_free(RB_REMOVE(tree_known_files, &known_files, data)); 2708 | } 2709 | 2710 | if (connection.verbosity > 1) 2711 | printf("\r\e[0K\r"); 2712 | 2713 | /* Print/prune any local files left. */ 2714 | 2715 | for (data = RB_MIN(tree_local_files, &local_files); data != NULL; data = next) { 2716 | next = RB_NEXT(tree_local_files, head, data); 2717 | 2718 | if (connection.trim_tree) { 2719 | /* exempt .git/ from being removed, as it may be used by svn2git tool */ 2720 | if(!strncmp(data->path, "/.git/", 6)) goto no_prune; 2721 | 2722 | char buf[1024]; 2723 | snprintf(buf, sizeof buf, "%s%s", connection.path_target, data->path); 2724 | if (strncmp(connection.path_work, buf, strlen(connection.path_work))) 2725 | prune(&connection, data->path); 2726 | } else { 2727 | if (connection.extra_files) 2728 | fprintf(stderr, " * %s%s\n", connection.path_target, data->path); 2729 | } 2730 | 2731 | no_prune:; 2732 | tree_node_free(RB_REMOVE(tree_local_files, &local_files, data)); 2733 | } 2734 | 2735 | /* Prune any empty local directories not found in the repository. */ 2736 | 2737 | if (connection.verbosity > 1) 2738 | fprintf(stderr, "\e[0K\r"); 2739 | 2740 | for (data = RB_MAX(tree_local_directories, &local_directories); data != NULL; data = next) { 2741 | next = RB_PREV(tree_local_directories, head, data); 2742 | 2743 | char buf[1024]; 2744 | snprintf(buf, sizeof buf, "%s/.git/", connection.path_target); 2745 | 2746 | if (strncmp(data->path, buf, strlen(buf)) && rmdir(data->path) == 0) 2747 | fprintf(stderr, " = %s\n", data->path); 2748 | 2749 | tree_node_free(RB_REMOVE(tree_local_directories, &local_directories, data)); 2750 | } 2751 | 2752 | /* Wrap it all up. */ 2753 | 2754 | if (close(connection.socket_descriptor) != 0) 2755 | if (errno != EBADF) 2756 | err(EXIT_FAILURE, "close connection failed"); 2757 | 2758 | remove(connection.known_files_old); 2759 | 2760 | if ((rename(connection.known_files_new, connection.known_files_old)) != 0) 2761 | err(EXIT_FAILURE, "Cannot rename %s", connection.known_files_old); 2762 | 2763 | if (connection.address) 2764 | free(connection.address); 2765 | 2766 | if (connection.root) 2767 | free(connection.root); 2768 | 2769 | if (connection.trunk) 2770 | free(connection.trunk); 2771 | 2772 | if (connection.branch) 2773 | free(connection.branch); 2774 | 2775 | if (connection.known_files) 2776 | free(connection.known_files); 2777 | 2778 | if (connection.path_target) 2779 | free(connection.path_target); 2780 | 2781 | if (connection.path_work) 2782 | free(connection.path_work); 2783 | 2784 | if (connection.ssl) { 2785 | SSL_shutdown(connection.ssl); 2786 | SSL_CTX_free(connection.ctx); 2787 | SSL_free(connection.ssl); 2788 | } 2789 | 2790 | free(connection.commit_author); 2791 | free(connection.commit_msg); 2792 | free(connection.commit_date); 2793 | 2794 | free(connection.known_files_old); 2795 | free(connection.known_files_new); 2796 | free(connection.response); 2797 | free(file); 2798 | 2799 | return (0); 2800 | } 2801 | 2802 | --------------------------------------------------------------------------------