diff options
author | Valery Kartel <valery.kartel@gmail.com> | 2015-09-24 16:17:31 +0300 |
---|---|---|
committer | Sören Tempel <soeren+git@soeren-tempel.net> | 2015-09-29 16:13:11 +0200 |
commit | fa6505dc69a2a6945324744d8134de0b186be808 (patch) | |
tree | 51ed8fedb90f372a5b783b2a2a7d2af6f7f890b2 | |
parent | 48cf8ee4d00068967848c3c9deb33b6c996c1697 (diff) | |
download | aports-fa6505dc69a2a6945324744d8134de0b186be808.tar.bz2 aports-fa6505dc69a2a6945324744d8134de0b186be808.tar.xz |
testing/csync2: upgrade to 2.0
-rw-r--r-- | testing/csync2/03-strlcpy_disable.patch | 21 | ||||
-rw-r--r-- | testing/csync2/APKBUILD | 94 | ||||
-rw-r--r-- | testing/csync2/csync2-1.34-librsync-1.0.0.patch | 35 | ||||
-rw-r--r-- | testing/csync2/csync2.initd | 13 | ||||
-rw-r--r-- | testing/csync2/csync2.post-install | 24 | ||||
-rw-r--r-- | testing/csync2/csync2.post-upgrade | 6 | ||||
-rw-r--r-- | testing/csync2/git.patch | 6640 | ||||
-rw-r--r-- | testing/csync2/longlong-format.patch | 31 | ||||
-rw-r--r-- | testing/csync2/rsync-strlcpy-disable.patch | 20 |
9 files changed, 132 insertions, 6752 deletions
diff --git a/testing/csync2/03-strlcpy_disable.patch b/testing/csync2/03-strlcpy_disable.patch deleted file mode 100644 index f0e9d45451..0000000000 --- a/testing/csync2/03-strlcpy_disable.patch +++ /dev/null @@ -1,21 +0,0 @@ ---- old/rsync.c -+++ new/rsync.c -@@ -49,6 +49,9 @@ - * - * @return index of the terminating byte. - **/ -+ -+/* disabled ** -+ - static size_t strlcpy(char *d, const char *s, size_t bufsize) - { - size_t len = strlen(s); -@@ -61,7 +64,7 @@ - } - return ret; - } -- -+*/ - - /* This has been taken from rsync sources: receiver.c */ - diff --git a/testing/csync2/APKBUILD b/testing/csync2/APKBUILD index f26da55cbc..5b32bad425 100644 --- a/testing/csync2/APKBUILD +++ b/testing/csync2/APKBUILD @@ -1,83 +1,65 @@ -# Maintainer: Natanael Copa <ncopa@alpinelinux.org> pkgname=csync2 -pkgver=1.34_git20111201 -_ver=${pkgver%_git*} -pkgrel=2 +pkgver=2.0 +pkgrel=0 pkgdesc="Cluster synchronization tool" url="http://oss.linbit.com/csync2/" arch="all" license="GPL-2+" -depends= -makedepends="librsync-dev gnutls-dev sqlite-dev autoconf automake bison flex" -install= -subpackages="$pkgname-doc" -source="http://oss.linbit.com/csync2/csync2-${_ver}.tar.gz - git.patch - 03-strlcpy_disable.patch - csync2-1.34-librsync-1.0.0.patch - " -# 01-csync2-sqlite3.patch -# 02-csync2-1.34-gnutls_pkgconfig.patch +makedepends="librsync-dev gnutls-dev sqlite-dev mysql-dev postgresql-dev" +subpackages="$pkgname-compare $pkgname-doc" +source="http://oss.linbit.com/$pkgname/$pkgname-$pkgver.tar.gz + longlong-format.patch + rsync-strlcpy-disable.patch + $pkgname.initd" +install="$pkgname.post-install $pkgname.post-upgrade" -_builddir="$srcdir"/$pkgname-$_ver -_giturl="http://git.linbit.com/csync2.git" +_builddir="$srcdir"/$pkgname-$pkgver - -snapshot() { - local _pkg=$pkgname-$pkgver.tar.gz - mkdir -p "$srcdir" - cd "$srcdir" - msg "Creating snapshot: $_pkg" - rm -rf ${_giturl##*/} - git clone --bare $_giturl || return 1 - git --git-dir ${_giturl##*/} archive -o $_pkg \ - --prefix=$pkgname-$pkgver/ HEAD \ - || return 1 - - msg "New snapshot: $_pkg" - if [ -n "$_upload" ]; then - msg "Uploading to $_upload" - scp $_pkg $_upload || return 1 - abump $pkgname-$_tag - fi - #abuild checksum -} - -prepare() { +prepare() { cd "$_builddir" for i in $source; do case $i in *.patch) msg $i; patch -p1 -i "$srcdir"/$i || return 1;; esac done - autoreconf --install --force } - + build() { cd "$_builddir" ./configure --prefix=/usr \ - --sysconfdir=/etc \ + --sysconfdir=/etc/$pkgname \ --localstatedir=/var \ + --docdir=/usr/share/doc/$pkgname \ --mandir=/usr/share/man \ - --infodir=/usr/share/info \ + --enable-postgres \ + --enable-mysql \ || return 1 make || return 1 } package() { cd "$_builddir" - make DESTDIR="$pkgdir" install + make DESTDIR="$pkgdir" install || return 1 + install -m755 -D "$srcdir"/$pkgname.initd "$pkgdir"/etc/init.d/$pkgname + cp doc/* "$pkgdir"/usr/share/doc/$pkgname/ } -md5sums="efc8a3548996b79cef2ad76af5e93cd8 csync2-1.34.tar.gz -bd4957d25e984518d929ae4036bf0ae9 git.patch -e6d8aaff70cf847b11873cd1cfaaa8f6 03-strlcpy_disable.patch -943227a4ca4bdd5ec37a315dba1844d3 csync2-1.34-librsync-1.0.0.patch" -sha256sums="32b250dd4a0353f71015c5c3961174b975dd5e799e4a084e8f6d00792bd8c833 csync2-1.34.tar.gz -7174ff0cb464b8f74c3816bfe3c5a89e9b876b50dcd6a7440b7488e3561668d7 git.patch -f4a44ca3f3a33a413a96ce837d5569c3d802bce4a6a960593b014d8dcbfbc136 03-strlcpy_disable.patch -320ac974d6bbebddea5bc08b92c944189733742e2db8c3580e193ca9a2b934ee csync2-1.34-librsync-1.0.0.patch" -sha512sums="a24154446740f3362c10a23d526c29e96292f2ad56a9ed2f11ca0a6d7afed3730d0e96f8ebc2c6cdcc17031aa0e368f6ba93084ad3cf5f4828fbc279e6f004d8 csync2-1.34.tar.gz -ff47b30e86f6266b4be85a47db52409169597d97e4ec086653af3341978cce5e477054e5746f3824aed1d8c7949e5f8451b2bd675442a9a2c9c5c519918438a9 git.patch -1b82b25acfb988cd8f92117f6ea4bc22247648448751adc0958dae0ade3c1eb065ad15f7104e4860ea2f08a91a368d68e3959ef5dfa278c254857382ebb64625 03-strlcpy_disable.patch -f44e1df35aefff2900a5983fd1e368864396757e1fcfd6e3f5d39a55c1bad6e42e8bca5f0e16aa641dff3b62a27d7d5413dc907bcd19b9d4de068fd7be7214b0 csync2-1.34-librsync-1.0.0.patch" +compare() { + subpkgdesc="csync2 compare script" + arch="noarch" + depents="bash perl-digest-md5" + mkdir -p "$subpkgdir"/usr/sbin + mv "$pkgdir"/usr/sbin/$pkgname-compare "$subpkgdir"/usr/sbin/ +} +md5sums="4069fc9e86e8508c392fe2862059eb91 csync2-2.0.tar.gz +8fc73254db29316f9125761d18f1ed46 longlong-format.patch +5908c4106d4866ab1c1f88f4c9482c9f rsync-strlcpy-disable.patch +05df8f2b877e7d8ca838df9e71bf2c65 csync2.initd" +sha256sums="11cb37380fb185bce0c22b804fec9b01c385d9d83cc528cfd48d748453834fa2 csync2-2.0.tar.gz +afb1317987cc4b81908bc437269162c7af23b34e6842306483da5b53efce2db3 longlong-format.patch +339c7f5d91ff7ed3e63de5a5e39cf17efd7533f9c460eb81db76003acb164017 rsync-strlcpy-disable.patch +77f6baf688fdb6938846e9f6e0e945d4085d2f0002661ff335e5c8ac39f0069a csync2.initd" +sha512sums="f91fd222f67affe9634471d341b43ff67854a6ed25b620301a454e98a79a9fb80b2a66eb8713546758fd08300d52751e5ca7472c696daa20ee11779b87a830f8 csync2-2.0.tar.gz +38a17cbf23cbccd4996ad1af049851e33179586e619e3f3edbfacbaa36662e44d916839acd59b1cfc67b3af9042c3258068ec9e5b57f7e26c00a41a0e6f0e148 longlong-format.patch +c44ad7e7fb7093f66fd9582b5309e3929dfdf7b76ef4d234d346fa25158f088b9436db9a1989d036d739570459867210d053af62125facb66e4f5202448c7bea rsync-strlcpy-disable.patch +96e5fb1a8865981a014f368b3a644659d048183534a72332718aa815fdf28180e758005e8ae7c438bb8b36134719c2597dd44fc09c85ba13e150f6d396d6b25b csync2.initd" diff --git a/testing/csync2/csync2-1.34-librsync-1.0.0.patch b/testing/csync2/csync2-1.34-librsync-1.0.0.patch deleted file mode 100644 index d46653d455..0000000000 --- a/testing/csync2/csync2-1.34-librsync-1.0.0.patch +++ /dev/null @@ -1,35 +0,0 @@ -Patch by Robert Scheck <robert@fedoraproject.org> for csync2 <= 1.34 based on the -patch by Roman Tereshonkov and Kari Hautio to avoid a build failure with librsync ->= 1.0.0 (which is a security bugfix release). The discussion and solution finding -can be found at https://bugs.launchpad.net/duplicity/+bug/1416344 (for duplicity). - ---- csync2-1.34/rsync.c 2007-07-24 23:04:18.000000000 +0200 -+++ csync2-1.34/rsync.c.librsync-1.0.0 2015-03-02 00:18:43.000000000 +0100 -@@ -134,8 +134,13 @@ - - if ( isreg ) { - csync_debug(3, "Running rs_sig_file() from librsync....\n"); -+#ifdef RS_DEFAULT_STRONG_LEN - result = rs_sig_file(basis_file, sig_file, - RS_DEFAULT_BLOCK_LEN, RS_DEFAULT_STRONG_LEN, &stats); -+#else /* librsync >= 1.0.0 */ -+ result = rs_sig_file(basis_file, sig_file, -+ RS_DEFAULT_BLOCK_LEN, 8, RS_MD4_SIG_MAGIC, &stats); -+#endif - if (result != RS_DONE) { - csync_debug(0, "Internal error from rsync library!\n"); - goto error; -@@ -216,8 +221,13 @@ - if ( !basis_file ) basis_file = fopen("/dev/null", "rb"); - - csync_debug(3, "Running rs_sig_file() from librsync..\n"); -+#ifdef RS_DEFAULT_STRONG_LEN - result = rs_sig_file(basis_file, sig_file, - RS_DEFAULT_BLOCK_LEN, RS_DEFAULT_STRONG_LEN, &stats); -+#else /* librsync >= 1.0.0 */ -+ result = rs_sig_file(basis_file, sig_file, -+ RS_DEFAULT_BLOCK_LEN, 8, RS_MD4_SIG_MAGIC, &stats); -+#endif - if (result != RS_DONE) - csync_fatal("Got an error from librsync, too bad!\n"); - diff --git a/testing/csync2/csync2.initd b/testing/csync2/csync2.initd new file mode 100644 index 0000000000..0caa339d61 --- /dev/null +++ b/testing/csync2/csync2.initd @@ -0,0 +1,13 @@ +#!/sbin/openrc-run + +name="$SVCNAME" +pidfile="/var/run/$SVCNAME.pid" +command="/usr/sbin/$SVCNAME" +command_args="-ii" +command_background="yes" +required_files="/etc/csync2/csync2.cfg /etc/csync2/csync2_ssl_key.pem /etc/csync2/csync2_ssl_cert.pem" + +depend() { + need net localmount + after firewall +} diff --git a/testing/csync2/csync2.post-install b/testing/csync2/csync2.post-install new file mode 100644 index 0000000000..3c2ecc1ecf --- /dev/null +++ b/testing/csync2/csync2.post-install @@ -0,0 +1,24 @@ +#!/bin/sh + +SSL="/etc/csync2/csync2_ssl_" +SERV="/etc/services" +INETD="/etc/inetd.conf" + +if [ ! -f ${SSL}key.pem -o ! -f ${SSL}cert.pem ]; then + openssl genrsa -out ${SSL}key.pem 1024 &> /dev/null + yes '' | openssl req -new -key ${SSL}key.pem -out ${SSL}cert.csr &> /dev/null + openssl x509 -req -days 3600 -in ${SSL}cert.csr -out ${SSL}cert.pem -signkey ${SSL}key.pem &> /dev/null + rm ${SSL}cert.csr +fi + +if [ ! -f ${SERV} ]; then + touch ${SERV} +fi +grep -q csync2 ${SERV} || echo -e "csync2\t\t30865/tcp" >> ${SERV} + +if [ ! -f ${INETD} ]; then + touch ${INETD} +fi +grep -q csync2 ${INETD} || echo "csync2 stream tcp nowait root /usr/sbin/csync2 csync2 -i" >> ${INETD} + +exit 0 diff --git a/testing/csync2/csync2.post-upgrade b/testing/csync2/csync2.post-upgrade new file mode 100644 index 0000000000..26f8e45124 --- /dev/null +++ b/testing/csync2/csync2.post-upgrade @@ -0,0 +1,6 @@ +#!/bin/sh + +mv /etc/csync2.cfg /etc/csync2/ &> /dev/null +mv /etc/csync2_ssl_*.pem /etc/csync2/ &> /dev/null + +exit 0 diff --git a/testing/csync2/git.patch b/testing/csync2/git.patch deleted file mode 100644 index 5d9b7094b6..0000000000 --- a/testing/csync2/git.patch +++ /dev/null @@ -1,6640 +0,0 @@ -diff --git a/AUTHORS b/AUTHORS -index 74faa05..45ed0d7 100644 ---- a/AUTHORS -+++ b/AUTHORS -@@ -1 +1,8 @@ -+LINBIT Information Technologies GmbH <http://www.linbit.com> - Clifford Wolf <clifford@clifford.at> -+ -+With contributions from: -+ -+Lars Ellenberg <lars.ellenberg@linbit.at> -+Johannes Thoma <johannes.thoma@gmx.at> -+Dennis Schafroth <dennis@schafroth.dk> -diff --git a/ChangeLog b/ChangeLog -index 52fa2a2..9d9f791 100644 ---- a/ChangeLog -+++ b/ChangeLog -@@ -1,2 +1,2 @@ - Please fetch the ChangeLog directly from the subversion repository: --svn log -v http://svn.clifford.at/csync2/ -+svn log -v http://svn.linbit.com/csync2/ -diff --git a/Makefile.am b/Makefile.am -index e3ec933..adbf68d 100644 ---- a/Makefile.am -+++ b/Makefile.am -@@ -23,16 +23,27 @@ man_MANS = csync2.1 - - csync2_SOURCES = action.c cfgfile_parser.y cfgfile_scanner.l check.c \ - checktxt.c csync2.c daemon.c db.c error.c getrealfn.c \ -- groups.c rsync.c update.c urlencode.c conn.c prefixsubst.c -+ groups.c rsync.c update.c urlencode.c conn.c prefixsubst.c \ -+ db_api.c db_sqlite.c db_sqlite2.c db_mysql.c db_postgres.c \ -+ csync2.h db_api.h db_mysql.h db_postgres.h db_sqlite.h db_sqlite2.h dl.h \ -+ csync2-compare \ -+ csync2.1 -+ -+EXTRA_DIST = csync2.cfg csync2.xinetd - - AM_YFLAGS = -d - BUILT_SOURCES = cfgfile_parser.h -+LIBS += -ldl - CLEANFILES = cfgfile_parser.c cfgfile_parser.h cfgfile_scanner.c \ -- private_librsync private_libsqlite config.log \ -- config.status config.h .deps/*.Po stamp-h1 Makefile -+ private_librsync private_libsqlite config.log -+ -+DISTCLEANFILES = config.status config.h .deps/*.Po stamp-h1 Makefile Makefile.in configure -+ -+dist-clean-local: -+ rm -rf autom4te.cache - --AM_CFLAGS= --AM_LDFLAGS= -+AM_CFLAGS=$(LIBGNUTLS_CFLAGS) -+AM_LDFLAGS=$(LIBGNUTLS_LIBS) - - if PRIVATE_LIBRSYNC - BUILT_SOURCES += private_librsync -@@ -41,13 +52,6 @@ if PRIVATE_LIBRSYNC - LIBS += -lprivatersync - endif - --if PRIVATE_LIBSQLITE -- BUILT_SOURCES += private_libsqlite -- AM_CFLAGS += -I$(shell test -f libsqlite.dir && cat libsqlite.dir || echo ==libsqlite==) -- AM_LDFLAGS += -L$(shell test -f libsqlite.dir && cat libsqlite.dir || echo ==libsqlite==) -- LIBS += -lprivatesqlite --endif -- - AM_CPPFLAGS = -D'DBDIR="$(localstatedir)/lib/csync2"' - AM_CPPFLAGS += -D'ETCDIR="$(sysconfdir)"' - -diff --git a/README b/README -index ed6eb6b..7dbbae1 100644 ---- a/README -+++ b/README -@@ -12,7 +12,7 @@ better have a look at Unison (http://www.cis.upenn.edu/~bcpierce/unison/) - too. - - See http://oss.linbit.com/ for more information on csync2. The csync2 --subversion tree can be found at http://svn.clifford.at/csync2/. -+subversion tree can be found at http://svn.linbit.com/csync2/. - - - Copyright -@@ -76,3 +76,25 @@ There is a csync2 mailing list: - It is recommended to subscribe to this list if you are using csync2 in - production environments. - -+Building csync2 -+=============== -+ -+You'll need the GNU autotools and a compiler toolchain (gcc) for -+building csync2. -+ -+First, run the autogen.sh script: -+ -+karin$ ./autogen.sh -+ -+Then run configure, use ./configure --help for more options: -+ -+karin$ ./configure -+ -+Then run make: -+ -+karin$ make -+ -+csync2 should be built now. Direct any questions to the csync2 mailing list -+(see above). -+ -+- Johannes -diff --git a/TODO b/TODO -index 6c02fc3..0ee83ff 100644 ---- a/TODO -+++ b/TODO -@@ -1 +1,74 @@ - Universal peace and a good attitude for everyone. -+ -+Check for mysql/mysql.h to exist in configure. -+ Done -+ -+DB abstraction: check for installed databases on configure -+ and enable/disable them for compilation. -+ -+Create MySQL database if it doesn't exist. -+ -+Implement table creation with schema support. -+ We don't have a schema table yet, add it when it is needed. -+ -+Have check return value for asprintf () .. have a macro that does a csync_fatal -+ if there is no memory. -+ -+Make database configurable. -+ -+From Dennis: -+Filename column is too short, but this is due to the fact that mysql 5 only -+supports keys length of max 1000 bytes. -+So the filename+peername must be below 333 UTF characters (since mysql looks at -+worst-case when generating the tables). -+ Sort of fixed. Fields are 4096 bytes now (highest MAXPATHLEN of all -+ supported platforms) but only the first 1000 chars are unique. -+ -+sqlite3:// url not working -+ It works but it needs an extra slash like in sqlite3:///var/lib/... -+ Now have a howto if slash is missing and database file is not found. -+ -+-a should be stronger than configured database in /etc/csync2.cfg -+ Works now. -+ -+test long filenames with mysql -+ Work now -+ -+From Dennis: -+Weird characters in filename cuts off the filename at the character. I have a -+danish letter (å encoded in iso-8859-1: \370) still present in my -+now UTF-8 filesystem names. -+ Couldn't reproduce tried with German umlauts. -+ -+--------------------------------------------------------------------------- -+ -+Test schema support for SQLite 2. -+ -+Have command to pipe connection through (for SSH support for example) -+ -+From Gerhard Rieger: -+If there are more than one node to sync with print nodes that are not reachable. -+ Done, test it -+ -+ Segfault when syncing a file where one side is a directory and the other one -+ is a link. -+ -+postgres support -+ -+dl_open for all sql related calls -+ we don't want to depend on libmysql/libsqlite/whatever on install. -+ TODO: how to express that we need at least one sql client library in Debian/RPM -+ -+Performance tests: when does it make sense to use mysql instead of sqlite? -+ -+Have schema version table. -+ -+Compile even when there is no libsqlite (mysql support only) -+ -+From Martin: Provide up-to-date packages. -+ Resuse build.sh script from drbd-proxy. -+ -+Build packages for all supported distros. -+ -+If include <dir> is missing error message is Permission denied, which is -+ irritating. -diff --git a/action.c b/action.c -index 438db5c..9ac8126 100644 ---- a/action.c -+++ b/action.c -@@ -38,14 +38,18 @@ void csync_schedule_commands(const char *filename, int islocal) - - while ( (g=csync_find_next(g, filename)) ) { - for (a=g->action; a; a=a->next) { -+ if ( !islocal && a->do_local_only ) -+ continue; - if ( islocal && !a->do_local ) - continue; - if (!a->pattern) - goto found_matching_pattern; -- for (p=a->pattern; p; p=p->next) -+ for (p=a->pattern; p; p=p->next) { -+ int fnm_pathname = p->star_matches_slashes ? 0 : FNM_PATHNAME; - if ( !fnmatch(p->pattern, filename, -- FNM_LEADING_DIR|FNM_PATHNAME) ) -+ FNM_LEADING_DIR|fnm_pathname) ) - goto found_matching_pattern; -+ } - continue; - found_matching_pattern: - for (c=a->command; c; c=c->next) -@@ -69,7 +73,7 @@ void csync_run_single_command(const char *command, const char *logfile) - "SELECT filename from action WHERE command = '%s' " - "and logfile = '%s'", command, logfile) - { -- textlist_add(&tl, SQL_V[0], 0); -+ textlist_add(&tl, SQL_V(0), 0); - } SQL_END; - - mark = strstr(command_clr, "%%"); -@@ -107,7 +111,7 @@ void csync_run_single_command(const char *command, const char *logfile) - /* 1 */ open(logfile_clr, O_WRONLY|O_CREAT|O_APPEND, 0666); - /* 2 */ open(logfile_clr, O_WRONLY|O_CREAT|O_APPEND, 0666); - -- execl("/bin/sh", "sh", "-c", real_command, 0); -+ execl("/bin/sh", "sh", "-c", real_command, NULL); - _exit(127); - } - -@@ -130,7 +134,7 @@ void csync_run_commands() - SQL_BEGIN("Checking for sceduled commands", - "SELECT command, logfile FROM action GROUP BY command, logfile") - { -- textlist_add2(&tl, SQL_V[0], SQL_V[1], 0); -+ textlist_add2(&tl, SQL_V(0), SQL_V(1), 0); - } SQL_END; - - for (t = tl; t != 0; t = t->next) -diff --git a/autogen.sh b/autogen.sh -index df9e797..85663ac 100755 ---- a/autogen.sh -+++ b/autogen.sh -@@ -18,9 +18,9 @@ - # along with this program; if not, write to the Free Software - # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - --aclocal-1.7 -+aclocal - autoheader --automake-1.7 --add-missing --copy -+automake --add-missing --copy - autoconf - - if [ "$1" = clean ]; then -@@ -32,5 +32,13 @@ if [ "$1" = clean ]; then - rm -rf config.guess config.sub - rm -rf cygwin/librsync-0.9.7.tar.gz - rm -rf cygwin/sqlite-2.8.16.tar.gz -+else -+ ./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc -+ -+ echo "" -+ echo "Configured as" -+ echo "./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc" -+ echo "" -+ echo "reconfigure, if you want it different" - fi - -diff --git a/cfgfile_parser.y b/cfgfile_parser.y -index 776bbcf..7f493ab 100644 ---- a/cfgfile_parser.y -+++ b/cfgfile_parser.y -@@ -33,6 +33,8 @@ struct csync_nossl *csync_nossl = 0; - int csync_ignore_uid = 0; - int csync_ignore_gid = 0; - int csync_ignore_mod = 0; -+unsigned csync_lock_timeout = 12; -+char *csync_tempdir = NULL; - - #ifdef __CYGWIN__ - int csync_lowercyg_disable = 0; -@@ -51,11 +53,12 @@ void yyerror(char *text) - static void new_group(char *name) - { - int static autonum = 1; -+ int rc; - struct csync_group *t = - calloc(sizeof(struct csync_group), 1); - - if (name == 0) -- asprintf(&name, "group_%d", autonum++); -+ rc = asprintf(&name, "group_%d", autonum++); - - t->next = csync_group; - t->auto_method = -1; -@@ -106,12 +109,17 @@ static void add_patt(int patterntype, char *pattern) - } - #endif - -+ /* strip trailing slashes from pattern */ - for (i=strlen(pattern)-1; i>0; i--) - if (pattern[i] == '/') - pattern[i] = 0; - else - break; - -+ /* if you use ** at least once anywhere in the pattern, -+ * _all_ stars in the pattern, even single ones, -+ * will match slashes. */ -+ t->star_matches_slashes = !!strstr(pattern, "**"); - t->isinclude = patterntype >= 1; - t->iscompare = patterntype >= 2; - t->pattern = pattern; -@@ -280,6 +288,7 @@ static void add_action_pattern(const char *pattern) - { - struct csync_group_action_pattern *t = - calloc(sizeof(struct csync_group_action_pattern), 1); -+ t->star_matches_slashes = !!strstr(pattern, "**"); - t->pattern = pattern; - t->next = csync_group->action->pattern; - csync_group->action->pattern = t; -@@ -304,6 +313,28 @@ static void set_action_dolocal() - csync_group->action->do_local = 1; - } - -+static void set_action_dolocal_only() -+{ -+ csync_group->action->do_local = 1; -+ csync_group->action->do_local_only = 1; -+} -+ -+static void set_lock_timeout(const char *timeout) -+{ -+ csync_lock_timeout = atoi(timeout); -+} -+ -+static void set_tempdir(const char *tempdir) -+{ -+ csync_tempdir = strdup(tempdir); -+} -+ -+static void set_database(const char *filename) -+{ -+ if (!csync_database) -+ csync_database = strdup(filename); -+} -+ - static void new_prefix(const char *pname) - { - struct csync_prefix *p = -@@ -392,10 +423,12 @@ static void disable_cygwin_lowercase_hack() - } - - %token TK_BLOCK_BEGIN TK_BLOCK_END TK_STEND TK_AT TK_AUTO --%token TK_NOSSL TK_IGNORE TK_GROUP TK_HOST TK_EXCL TK_INCL TK_COMP TK_KEY -+%token TK_NOSSL TK_IGNORE TK_GROUP TK_HOST TK_EXCL TK_INCL TK_COMP TK_KEY TK_DATABASE - %token TK_ACTION TK_PATTERN TK_EXEC TK_DOLOCAL TK_LOGFILE TK_NOCYGLOWER - %token TK_PREFIX TK_ON TK_COLON TK_POPEN TK_PCLOSE --%token TK_BAK_DIR TK_BAK_GEN -+%token TK_BAK_DIR TK_BAK_GEN TK_DOLOCALONLY -+%token TK_TEMPDIR -+%token TK_LOCK_TIMEOUT - %token <txt> TK_STRING - - %% -@@ -413,9 +446,15 @@ block: - { } - | TK_NOSSL TK_STRING TK_STRING TK_STEND - { new_nossl($2, $3); } -+| TK_DATABASE TK_STRING TK_STEND -+ { set_database($2); } -+| TK_TEMPDIR TK_STRING TK_STEND -+ { set_tempdir($2); } - | TK_IGNORE ignore_list TK_STEND - | TK_NOCYGLOWER TK_STEND - { disable_cygwin_lowercase_hack(); } -+| TK_LOCK_TIMEOUT TK_STRING TK_STEND -+ { set_lock_timeout($2); } - ; - - ignore_list: -@@ -517,6 +556,8 @@ action_stmt: - { set_action_logfile($2); } - | TK_DOLOCAL - { set_action_dolocal(); } -+| TK_DOLOCALONLY -+ { set_action_dolocal_only(); } - ; - - action_pattern_list: -diff --git a/cfgfile_scanner.l b/cfgfile_scanner.l -index 77daf5f..5e93f7c 100644 ---- a/cfgfile_scanner.l -+++ b/cfgfile_scanner.l -@@ -25,9 +25,13 @@ - #define MAX_INCLUDE_DEPTH 10 - YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH]; - int include_stack_ptr = 0; -+ -+#define YY_NO_INPUT 1 -+#define YY_NO_UNPUT 1 - %} - - %option noyywrap yylineno -+%option nounput - %x STRING INCL - - %% -@@ -42,6 +46,7 @@ int include_stack_ptr = 0; - - "nossl" { return TK_NOSSL; } - "ignore" { return TK_IGNORE; } -+"database" { return TK_DATABASE; } - - "group" { return TK_GROUP; } - "host" { return TK_HOST; } -@@ -56,10 +61,13 @@ int include_stack_ptr = 0; - "exec" { return TK_EXEC; } - "logfile" { return TK_LOGFILE; } - "do-local" { return TK_DOLOCAL; } -+"do-local-only" { return TK_DOLOCALONLY; } - - "prefix" { return TK_PREFIX; } - "on" { return TK_ON; } - -+"lock-timeout" { return TK_LOCK_TIMEOUT; } -+"tempdir" { return TK_TEMPDIR; } - "backup-directory" { return TK_BAK_DIR; } - "backup-generations" { return TK_BAK_GEN; } - -diff --git a/check.c b/check.c -index 360abd3..c5b9f32 100644 ---- a/check.c -+++ b/check.c -@@ -99,15 +99,20 @@ void csync_mark(const char *file, const char *thispeer, const char *peerfilter) - - csync_debug(1, "Marking file as dirty: %s\n", file); - for (pl_idx=0; pl[pl_idx].peername; pl_idx++) -- if (!peerfilter || !strcmp(peerfilter, pl[pl_idx].peername)) -+ if (!peerfilter || !strcmp(peerfilter, pl[pl_idx].peername)) { -+ SQL("Deleting old dirty file entries", -+ "DELETE FROM dirty WHERE filename = '%s' AND peername = '%s'", -+ url_encode(file), -+ url_encode(pl[pl_idx].peername)); -+ - SQL("Marking File Dirty", -- "%s INTO dirty (filename, force, myname, peername) " -+ "INSERT INTO dirty (filename, forced, myname, peername) " - "VALUES ('%s', %s, '%s', '%s')", -- csync_new_force ? "REPLACE" : "INSERT", - url_encode(file), - csync_new_force ? "1" : "0", - url_encode(pl[pl_idx].myname), - url_encode(pl[pl_idx].peername)); -+ } - - free(pl); - } -@@ -122,21 +127,83 @@ int csync_check_pure(const char *filename) - if (!csync_lowercyg_disable) - return 0; - #endif -- - struct stat sbuf; -- int i=0; -+ int dir_len = 0; -+ int i; -+ int same_len; -+ -+ /* single entry last query cache -+ * to speed up checks from deep subdirs */ -+ static struct { -+ /* store inclusive trailing slash for prefix match */ -+ char *path; -+ /* strlen(path) */ -+ int len; -+ /* cached return value */ -+ int has_symlink; -+ } cached; -+ -+ for (i = 0; filename[i]; i++) -+ if (filename[i] == '/') -+ dir_len = i+1; -+ -+ if (dir_len <= 1) /* '/' a symlink? hardly. */ -+ return 0; -+ -+ /* identical prefix part */ -+ for (i = 0; i < dir_len && i < cached.len; i++) -+ if (filename[i] != cached.path[i]) -+ break; -+ -+ /* backtrack to slash */ -+ for (--i; i >= 0 && cached.path[i] != '/'; --i); -+ ; - -- while (filename[i]) i++; -+ same_len = i+1; -+ -+ csync_debug(3, " check: %s %u, %s %u, %u.\n", filename, dir_len, cached.path, cached.len, same_len); -+ /* exact match? */ -+ if (dir_len == same_len && same_len == cached.len) -+ return cached.has_symlink; - - { /* new block for myfilename[] */ -- char myfilename[i+1]; -- memcpy(myfilename, filename, i+1); -- while (1) { -- while (myfilename[i] != '/') -- if (--i <= 0) return 0; -+ char myfilename[dir_len+1]; -+ char *to_be_cached; -+ int has_symlink = 0; -+ memcpy(myfilename, filename, dir_len); -+ myfilename[dir_len] = '\0'; -+ to_be_cached = strdup(myfilename); -+ i = dir_len-1; -+ while (i) { -+ for (; i && myfilename[i] != '/'; --i) -+ ; -+ -+ if (i <= 1) -+ break; -+ -+ if (i+1 == same_len) { -+ if (same_len == cached.len) { -+ /* exact match */ -+ has_symlink = cached.has_symlink; -+ break; -+ } else if (!cached.has_symlink) -+ /* prefix of something 'pure' */ -+ break; -+ } -+ - myfilename[i]=0; -- if ( lstat_strict(prefixsubst(myfilename), &sbuf) || S_ISLNK(sbuf.st_mode) ) return 1; -+ if (lstat_strict(prefixsubst(myfilename), &sbuf) || S_ISLNK(sbuf.st_mode)) { -+ has_symlink = 1; -+ break; -+ } - } -+ if (to_be_cached) { /* strdup can fail. So what. */ -+ free(cached.path); -+ cached.path = to_be_cached; -+ cached.len = dir_len; -+ cached.has_symlink = has_symlink; -+ } -+ return has_symlink; - } - } - -@@ -148,18 +215,22 @@ void csync_check_del(const char *file, int recursive, int init_run) - - if ( recursive ) { - if ( !strcmp(file, "/") ) -- asprintf(&where_rec, "or 1"); -+ ASPRINTF(&where_rec, "OR 1=1"); - else -- asprintf(&where_rec, "or (filename > '%s/' " -- "and filename < '%s0')", -- url_encode(file), url_encode(file)); -+ ASPRINTF(&where_rec, "UNION ALL SELECT filename from file where filename > '%s/' " -+ "and filename < '%s0'", -+ url_encode(file), url_encode(file)); - } - - SQL_BEGIN("Checking for removed files", - "SELECT filename from file where " - "filename = '%s' %s ORDER BY filename", url_encode(file), where_rec) - { -- const char *filename = url_decode(SQL_V[0]); -+ const char *filename = url_decode(SQL_V(0)); -+ -+ if (!csync_match_file(filename)) -+ continue; -+ - if ( lstat_strict(prefixsubst(filename), &st) != 0 || csync_check_pure(filename) ) - textlist_add(&tl, filename, 0); - } SQL_END; -@@ -231,7 +302,7 @@ int csync_check_mod(const char *file, int recursive, int ignnoent, int init_run) - "filename = '%s'", url_encode(file)) - { - if ( !csync_cmpchecktxt(checktxt, -- url_decode(SQL_V[0])) ) { -+ url_decode(SQL_V(0))) ) { - csync_debug(2, "File has changed: %s\n", file); - this_is_dirty = 1; - } -@@ -243,6 +314,10 @@ int csync_check_mod(const char *file, int recursive, int ignnoent, int init_run) - } SQL_END; - - if ( this_is_dirty && !csync_compare_mode ) { -+ SQL("Deleting old file entry", -+ "DELETE FROM file WHERE filename = '%s'", -+ url_encode(file)); -+ - SQL("Adding or updating file entry", - "INSERT INTO file (filename, checktxt) " - "VALUES ('%s', '%s')", -diff --git a/configure.ac b/configure.ac -index 6ec6136..8989a33 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -17,15 +17,15 @@ - # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - # Process this file with autoconf to produce a configure script. --AC_INIT(csync2, 1.34, clifford@clifford.at) -+AC_INIT(csync2, 2.0rc1, csync2@lists.linbit.com) - AM_INIT_AUTOMAKE - - AC_CONFIG_SRCDIR(csync2.c) - AM_CONFIG_HEADER(config.h) - - # Use /etc and /var instead of $prefix/... --test "$localstatedir" = '${prefix}/var' && localstatedir=/var --test "$sysconfdir" = '${prefix}/etc' && sysconfdir=/etc -+# test "$localstatedir" = '${prefix}/var' && localstatedir=/var -+# test "$sysconfdir" = '${prefix}/etc' && sysconfdir=/etc - - # Checks for programs. - AC_PROG_CC -@@ -33,6 +33,9 @@ AC_PROG_INSTALL - AC_PROG_YACC - AM_PROG_LEX - -+# check for large file support -+AC_SYS_LARGEFILE -+ - # Check for librsync. - AC_ARG_WITH([librsync-source], - AS_HELP_STRING([--with-librsync-source=source-tar-file], -@@ -42,35 +45,78 @@ AC_ARG_WITH([librsync-source], - ) - AM_CONDITIONAL([PRIVATE_LIBRSYNC], [test -n "$librsync_source_file"]) - --# Check for libsqlite. --AC_ARG_WITH([libsqlite-source], -- AS_HELP_STRING([--with-libsqlite-source=source-tar-file], -- [build this libsqlite and link statically against it (hack! hack!)]), -- AC_SUBST([libsqlite_source_file], $withval), -- AC_CHECK_LIB([sqlite], [sqlite_exec], , [AC_MSG_ERROR(libsqlite is required)]) --) --AM_CONDITIONAL([PRIVATE_LIBSQLITE], [test -n "$libsqlite_source_file"]) -+AC_ARG_ENABLE([sqlite], -+ [AC_HELP_STRING([--enable-sqlite], -+ [enable/disable sqlite 2 support (default is disabled)])], -+ [], [ enable_sqlite=no ]) -+ -+if test "$enable_sqlite" == yes -+then -+ AC_CHECK_HEADERS([sqlite.h], , [AC_MSG_ERROR([[SQLite header not found; install libsqlite-dev and dependencies for SQLite 2 support]])]) -+ -+ AC_DEFINE([HAVE_SQLITE], 1, [Define if sqlite 2 support is wanted]) -+fi -+ -+AC_ARG_ENABLE([sqlite3], -+ [AC_HELP_STRING([--disable-sqlite3], -+ [enable/disable sqlite3 support (default is enabled)])], -+ [], [ enable_sqlite3=yes ]) -+ -+if test "$enable_sqlite3" == yes -+then -+ AC_CHECK_HEADERS([sqlite3.h], , [AC_MSG_ERROR([[SQLite header not found; install libsqlite3-dev and dependencies for SQLite 3 support]])]) -+ -+ AC_DEFINE([HAVE_SQLITE3], 1, [Define if sqlite3 support is wanted]) -+fi - - AC_ARG_ENABLE([gnutls], -- [AC_HELP_STRING([--disable-gnutls], -- [enable/disable GNU TLS support (default is enabled)])], -+ [AS_HELP_STRING([--disable-gnutls],[enable/disable GNU TLS support (default is enabled)])], - [], [ enable_gnutls=yes ]) - - if test "$enable_gnutls" != no - then -+ PKG_PROG_PKG_CONFIG -+ PKG_CHECK_MODULES([LIBGNUTLS], [gnutls >= 2.6.0], [ -+ AC_DEFINE([HAVE_LIBGNUTLS], 1, [Define to 1 when using GNU TLS library]) -+ ]) -+fi - -- # Check for gnuTLS. -- AM_PATH_LIBGNUTLS(1.0.0, , [ AC_MSG_ERROR([[gnutls not found; install gnutls, gnutls-openssl and libtasn1 packages for your system or run configure with --disable-gnutls]]) ]) -+AC_ARG_ENABLE([mysql], -+ [AC_HELP_STRING([--enable-mysql], -+ [enable/disable MySQL support (default is disabled)])], -+ [], [ enable_mysql=no ]) - -+AC_ARG_ENABLE([postgres], -+ [AC_HELP_STRING([--enable-postgres], -+ [enable/disable Postgres support (default is disabled)])], -+ [], [ enable_postgres=no ]) -+ -+if test "$enable_mysql" == yes -+then -+ # Check for mysql. - # This is a bloody hack for fedora core -- CFLAGS="$CFLAGS $LIBGNUTLS_CFLAGS" -- LIBS="$LIBS $LIBGNUTLS_LIBS -ltasn1" -+ CFLAGS="$CFLAGS `mysql_config --cflags`" -+ -+ # Check MySQL development header -+ AC_CHECK_HEADERS([mysql/mysql.h], , [AC_MSG_ERROR([[mysql header not found; install mysql-devel and dependencies for MySQL Support]])]) - -- # Check gnuTLS SSL compatibility lib. -- AC_CHECK_LIB([gnutls-openssl], [SSL_new], , [AC_MSG_ERROR([[gnutls-openssl not found; install gnutls, gnutls-openssl and libtasn1 packages for your system or run configure with --disable-gnutls]])]) -+ AC_DEFINE([HAVE_MYSQL], 1, [Define if mysql support is wanted]) -+fi -+ -+if test "$enable_postgres" == yes -+then -+ AC_CHECK_HEADERS([postgresql/libpq-fe.h], , [AC_MSG_ERROR([[postgres header not found; install libpq-dev and dependencies for Postgres support]])]) - -+ AC_DEFINE([HAVE_POSTGRES], 1, [Define if postgres support is wanted]) -+fi -+ -+# at least one db backend must be configured. -+ -+if test "$enable_postgres" != yes && test "$enable_mysql" != yes && -+ test "$enable_sqlite3" != yes && test "$enable_sqlite" != yes -+then -+ AC_MSG_ERROR([No database backend configured. Please enable either sqlite, sqlite3, mysql or postgres.]) - fi - - AC_CONFIG_FILES([Makefile]) - AC_OUTPUT -- -diff --git a/conn.c b/conn.c -index 6f8dfdc..8dda10d 100644 ---- a/conn.c -+++ b/conn.c -@@ -30,52 +30,77 @@ - #include <netdb.h> - #include <errno.h> - --#ifdef HAVE_LIBGNUTLS_OPENSSL -+#ifdef HAVE_LIBGNUTLS - # include <gnutls/gnutls.h> --# include <gnutls/openssl.h> -+# include <gnutls/x509.h> - #endif - - int conn_fd_in = -1; - int conn_fd_out = -1; - int conn_clisok = 0; - --#ifdef HAVE_LIBGNUTLS_OPENSSL -+#ifdef HAVE_LIBGNUTLS - int csync_conn_usessl = 0; - --SSL_METHOD *conn_ssl_meth; --SSL_CTX *conn_ssl_ctx; --SSL *conn_ssl; -+static gnutls_session_t conn_tls_session; -+static gnutls_certificate_credentials_t conn_x509_cred; - #endif - -+ -+/* getaddrinfo stuff mostly copied from its manpage */ -+int conn_connect(const char *peername) -+{ -+ struct addrinfo hints; -+ struct addrinfo *result, *rp; -+ int sfd, s; -+ -+ /* Obtain address(es) matching host/port */ -+ memset(&hints, 0, sizeof(struct addrinfo)); -+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ -+ hints.ai_socktype = SOCK_STREAM; -+ hints.ai_flags = 0; -+ hints.ai_protocol = 0; /* Any protocol */ -+ -+ s = getaddrinfo(peername, csync_port, &hints, &result); -+ if (s != 0) { -+ csync_debug(1, "Cannot resolve peername, getaddrinfo: %s\n", gai_strerror(s)); -+ return -1; -+ } -+ -+ /* getaddrinfo() returns a list of address structures. -+ Try each address until we successfully connect(2). -+ If socket(2) (or connect(2)) fails, we (close the socket -+ and) try the next address. */ -+ -+ for (rp = result; rp != NULL; rp = rp->ai_next) { -+ sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); -+ if (sfd == -1) -+ continue; -+ -+ if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) -+ break; /* Success */ -+ -+ close(sfd); -+ } -+ freeaddrinfo(result); /* No longer needed */ -+ -+ if (rp == NULL) /* No address succeeded */ -+ return -1; -+ -+ return sfd; -+} -+ - int conn_open(const char *peername) - { -- struct sockaddr_in sin; -- struct hostent *hp; - int on = 1; - -- hp = gethostbyname(peername); -- if ( ! hp ) { -- csync_debug(1, "Can't resolve peername.\n"); -- return -1; -- } -- -- conn_fd_in = socket(hp->h_addrtype, SOCK_STREAM, 0); -+ conn_fd_in = conn_connect(peername); - if (conn_fd_in < 0) { - csync_debug(1, "Can't create socket.\n"); - return -1; - } - -- sin.sin_family = hp->h_addrtype; -- bcopy(hp->h_addr, &sin.sin_addr, hp->h_length); -- sin.sin_port = htons(csync_port); -- -- if (connect(conn_fd_in, (struct sockaddr *)&sin, sizeof (sin)) < 0) { -- csync_debug(1, "Can't connect to remote host.\n"); -- close(conn_fd_in); conn_fd_in = -1; -- return -1; -- } -- -- if (setsockopt(conn_fd_in, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on) ) < 0 ) { -+ if (setsockopt(conn_fd_in, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on) ) < 0) { - csync_debug(1, "Can't set TCP_NODELAY option on TCP socket.\n"); - close(conn_fd_in); conn_fd_in = -1; - return -1; -@@ -83,10 +108,9 @@ int conn_open(const char *peername) - - conn_fd_out = conn_fd_in; - conn_clisok = 1; --#ifdef HAVE_LIBGNUTLS_OPENSSL -+#ifdef HAVE_LIBGNUTLS - csync_conn_usessl = 0; - #endif -- - return 0; - } - -@@ -97,12 +121,13 @@ int conn_set(int infd, int outfd) - conn_fd_in = infd; - conn_fd_out = outfd; - conn_clisok = 1; --#ifdef HAVE_LIBGNUTLS_OPENSSL -+#ifdef HAVE_LIBGNUTLS - csync_conn_usessl = 0; - #endif - - // when running in server mode, this has been done already - // in csync2.c with more restrictive error handling.. -+ // FIXME don't even try in "ssh" mode - if ( setsockopt(conn_fd_out, IPPROTO_TCP, TCP_NODELAY, &on, (socklen_t) sizeof(on)) < 0 ) - csync_debug(1, "Can't set TCP_NODELAY option on TCP socket.\n"); - -@@ -110,43 +135,106 @@ int conn_set(int infd, int outfd) - } - - --#ifdef HAVE_LIBGNUTLS_OPENSSL -+#ifdef HAVE_LIBGNUTLS -+ -+static void ssl_log(int level, const char* msg) -+{ csync_debug(level, "%s", msg); } - --char *ssl_keyfile = ETCDIR "/csync2_ssl_key.pem"; --char *ssl_certfile = ETCDIR "/csync2_ssl_cert.pem"; -+static const char *ssl_keyfile = ETCDIR "/csync2_ssl_key.pem"; -+static const char *ssl_certfile = ETCDIR "/csync2_ssl_cert.pem"; - - int conn_activate_ssl(int server_role) - { -- static int sslinit = 0; -+ gnutls_alert_description_t alrt; -+ int err; - - if (csync_conn_usessl) - return 0; - -- if (!sslinit) { -- SSL_load_error_strings(); -- SSL_library_init(); -- sslinit=1; -- } -+ gnutls_global_init(); -+ gnutls_global_set_log_function(ssl_log); -+ gnutls_global_set_log_level(10); - -- conn_ssl_meth = (server_role ? SSLv23_server_method : SSLv23_client_method)(); -- conn_ssl_ctx = SSL_CTX_new(conn_ssl_meth); -+ gnutls_certificate_allocate_credentials(&conn_x509_cred); - -- if (SSL_CTX_use_PrivateKey_file(conn_ssl_ctx, ssl_keyfile, SSL_FILETYPE_PEM) <= 0) -- csync_fatal("SSL: failed to use key file %s.\n", ssl_keyfile); -+ err = gnutls_certificate_set_x509_key_file(conn_x509_cred, ssl_certfile, ssl_keyfile, GNUTLS_X509_FMT_PEM); -+ if(err != GNUTLS_E_SUCCESS) { -+ gnutls_certificate_free_credentials(conn_x509_cred); -+ gnutls_global_deinit(); - -- if (SSL_CTX_use_certificate_file(conn_ssl_ctx, ssl_certfile, SSL_FILETYPE_PEM) <= 0) -- csync_fatal("SSL: failed to use certificate file %s.\n", ssl_certfile); -+ csync_fatal( -+ "SSL: failed to use key file %s and/or certificate file %s: %s (%s)\n", -+ ssl_keyfile, -+ ssl_certfile, -+ gnutls_strerror(err), -+ gnutls_strerror_name(err) -+ ); -+ } - -- if (! (conn_ssl = SSL_new(conn_ssl_ctx)) ) -- csync_fatal("Creating a new SSL handle failed.\n"); -+ if(server_role) { -+ gnutls_certificate_free_cas(conn_x509_cred); - -- gnutls_certificate_server_set_request(conn_ssl->gnutls_state, GNUTLS_CERT_REQUIRE); -+ if(gnutls_certificate_set_x509_trust_file(conn_x509_cred, ssl_certfile, GNUTLS_X509_FMT_PEM) < 1) { -+ gnutls_certificate_free_credentials(conn_x509_cred); -+ gnutls_global_deinit(); - -- SSL_set_rfd(conn_ssl, conn_fd_in); -- SSL_set_wfd(conn_ssl, conn_fd_out); -+ csync_fatal( -+ "SSL: failed to use certificate file %s as CA.\n", -+ ssl_certfile -+ ); -+ } -+ } else -+ gnutls_certificate_free_ca_names(conn_x509_cred); -+ -+ gnutls_init(&conn_tls_session, (server_role ? GNUTLS_SERVER : GNUTLS_CLIENT)); -+ gnutls_priority_set_direct(conn_tls_session, "PERFORMANCE", NULL); -+ gnutls_credentials_set(conn_tls_session, GNUTLS_CRD_CERTIFICATE, conn_x509_cred); - -- if ( (server_role ? SSL_accept : SSL_connect)(conn_ssl) < 1 ) -- csync_fatal("Establishing SSL connection failed.\n"); -+ if(server_role) { -+ gnutls_certificate_send_x509_rdn_sequence(conn_tls_session, 0); -+ gnutls_certificate_server_set_request(conn_tls_session, GNUTLS_CERT_REQUIRE); -+ } -+ -+ gnutls_transport_set_ptr2( -+ conn_tls_session, -+ (gnutls_transport_ptr_t)conn_fd_in, -+ (gnutls_transport_ptr_t)conn_fd_out -+ ); -+ -+ err = gnutls_handshake(conn_tls_session); -+ switch(err) { -+ case GNUTLS_E_SUCCESS: -+ break; -+ -+ case GNUTLS_E_WARNING_ALERT_RECEIVED: -+ alrt = gnutls_alert_get(conn_tls_session); -+ fprintf( -+ csync_debug_out, -+ "SSL: warning alert received from peer: %d (%s).\n", -+ alrt, gnutls_alert_get_name(alrt) -+ ); -+ break; -+ -+ case GNUTLS_E_FATAL_ALERT_RECEIVED: -+ alrt = gnutls_alert_get(conn_tls_session); -+ fprintf( -+ csync_debug_out, -+ "SSL: fatal alert received from peer: %d (%s).\n", -+ alrt, gnutls_alert_get_name(alrt) -+ ); -+ -+ default: -+ gnutls_bye(conn_tls_session, GNUTLS_SHUT_RDWR); -+ gnutls_deinit(conn_tls_session); -+ gnutls_certificate_free_credentials(conn_x509_cred); -+ gnutls_global_deinit(); -+ -+ csync_fatal( -+ "SSL: handshake failed: %s (%s)\n", -+ gnutls_strerror(err), -+ gnutls_strerror_name(err) -+ ); -+ } - - csync_conn_usessl = 1; - -@@ -155,15 +243,15 @@ int conn_activate_ssl(int server_role) - - int conn_check_peer_cert(const char *peername, int callfatal) - { -- const X509 *peercert; -+ const gnutls_datum_t *peercerts; -+ unsigned npeercerts; - int i, cert_is_ok = -1; - - if (!csync_conn_usessl) - return 1; - -- peercert = SSL_get_peer_certificate(conn_ssl); -- -- if (!peercert || peercert->size <= 0) { -+ peercerts = gnutls_certificate_get_peers(conn_tls_session, &npeercerts); -+ if(peercerts == NULL || npeercerts == 0) { - if (callfatal) - csync_fatal("Peer did not provide an SSL X509 cetrificate.\n"); - csync_debug(1, "Peer did not provide an SSL X509 cetrificate.\n"); -@@ -171,17 +259,17 @@ int conn_check_peer_cert(const char *peername, int callfatal) - } - - { -- char certdata[peercert->size*2 + 1]; -+ char certdata[2*peercerts[0].size + 1]; - -- for (i=0; i<peercert->size; i++) -- sprintf(certdata+i*2, "%02X", peercert->data[i]); -- certdata[peercert->size*2] = 0; -+ for (i=0; i<peercerts[0].size; i++) -+ sprintf(&certdata[2*i], "%02X", peercerts[0].data[i]); -+ certdata[2*i] = 0; - - SQL_BEGIN("Checking peer x509 certificate.", - "SELECT certdata FROM x509_cert WHERE peername = '%s'", - url_encode(peername)) - { -- if (!strcmp(SQL_V[0], certdata)) -+ if (!strcmp(SQL_V(0), certdata)) - cert_is_ok = 1; - else - cert_is_ok = 0; -@@ -215,14 +303,19 @@ int conn_check_peer_cert(const char *peername, int callfatal) - return 1; - } - --#endif /* HAVE_LIBGNUTLS_OPENSSL */ -+#endif /* HAVE_LIBGNUTLS */ - - int conn_close() - { - if ( !conn_clisok ) return -1; - --#ifdef HAVE_LIBGNUTLS_OPENSSL -- if ( csync_conn_usessl ) SSL_free(conn_ssl); -+#ifdef HAVE_LIBGNUTLS -+ if ( csync_conn_usessl ) { -+ gnutls_bye(conn_tls_session, GNUTLS_SHUT_RDWR); -+ gnutls_deinit(conn_tls_session); -+ gnutls_certificate_free_credentials(conn_x509_cred); -+ gnutls_global_deinit(); -+ } - #endif - - if ( conn_fd_in != conn_fd_out) close(conn_fd_in); -@@ -237,9 +330,9 @@ int conn_close() - - static inline int READ(void *buf, size_t count) - { --#ifdef HAVE_LIBGNUTLS_OPENSSL -+#ifdef HAVE_LIBGNUTLS - if (csync_conn_usessl) -- return SSL_read(conn_ssl, buf, count); -+ return gnutls_record_recv(conn_tls_session, buf, count); - else - #endif - return read(conn_fd_in, buf, count); -@@ -249,9 +342,9 @@ static inline int WRITE(const void *buf, size_t count) - { - static int n, total; - --#ifdef HAVE_LIBGNUTLS_OPENSSL -+#ifdef HAVE_LIBGNUTLS - if (csync_conn_usessl) -- return SSL_write(conn_ssl, buf, count); -+ return gnutls_record_send(conn_tls_session, buf, count); - else - #endif - { -@@ -302,7 +395,7 @@ int conn_raw_read(void *buf, size_t count) - return 0; - } - --void conn_debug(const char *name, const unsigned char*buf, size_t count) -+void conn_debug(const char *name, const char*buf, size_t count) - { - int i; - -@@ -365,9 +458,9 @@ void conn_printf(const char *fmt, ...) - conn_write(buffer, size); - } - --int conn_gets(char *s, int size) -+size_t conn_gets(char *s, size_t size) - { -- int i=0; -+ size_t i=0; - - while (i<size-1) { - int rc = conn_raw_read(s+i, 1); -diff --git a/contrib/csync2id.pl b/contrib/csync2id.pl -new file mode 100644 -index 0000000..1b3a5fa ---- /dev/null -+++ b/contrib/csync2id.pl -@@ -0,0 +1,359 @@ -+#!/usr/bin/perl -w -+# Copyright: telegraaf (NL) -+# Author: ard@telegraafnet.nl -+# License: GPL v2 or higher -+use strict; -+use Linux::Inotify2; -+use Data::Dumper; -+use File::Find; -+use POSIX qw(uname :sys_wait_h); -+use Sys::Syslog; -+use Net::Server::Daemonize qw(daemonize); -+use IO::Select; -+use Fcntl; -+ -+ -+my $program="csync2id"; -+my $daemonize=1; -+my $usesyslog=1; -+my $pidfile='/var/run/csync2id.pid'; -+my $pidfileboot='/var/run/csync2id.boot.pid'; -+ -+################################################################################ -+# Config items -+# Overridden by /etc/csync2id.cfg -+# Normal config in /etc/csync2id.cfg: -+# -+# @::dirs=qw( /data1 /data2 ); -+# 1; -+# -+# csyncdirhint: preferred hint command for directories (a single directory name -+# will be added) -+# csyncfilehint: preferred hint command for files (at most $x filenames will be appended) -+# csynccheck: preferred command scheduled right after the hint, or after a timeout -+# csyncupdate: preferred command scheduled right after the check -+# debug: log debug lines -+# statsdir: file to log the number of watched directories -+# statchanges: file to log the number of file change events -+# statsretry: file to log the number of retries needed so far for the hint -+# dirs: an array of directories which need to be watched recursively -+################################################################################ -+ -+$::csynchintmaxargs=20; -+@::csyncdirhint=("/usr/sbin/csync2", "-B","-A","-rh"); -+@::csyncfilehint=("/usr/sbin/csync2", "-B","-A","-h"); -+@::csynccheck=("/usr/sbin/csync2", "-B","-A","-c"); -+@::csyncupdate=("/usr/sbin/csync2", "-B","-A","-u"); -+$::debug=3; -+$::statsdir="/dev/shm/csyncstats/dirs"; -+$::statschanges="/dev/shm/csyncstats/changes"; -+$::statsretry="/dev/shm/csyncstats/retry"; -+@::dirs=(); -+require "/etc/csync2id.cfg"; -+ -+$daemonize && daemonize(0,0,$pidfileboot); -+$usesyslog && openlog("$program",'pid','daemon'); -+ -+use constant { LOGERR => 0, LOGWARN => 1, LOGINFO =>2, LOGDEBUG=>3,LOGSLOTS=>256 }; -+my %prios=( 0 => 'err', 1 => 'warning', 2 => 'info', default => 'debug' ); -+sub logger { -+ my($level,@args)=@_; -+ my ($prio)=$prios{$level}||$prios{'default'}; # :$prios{'default'}; -+ if($usesyslog) { -+ syslog($prio,@args) if (($level<= LOGDEBUG && $level<=$::debug)||($::debug>=LOGDEBUG && $level&$::debug)) -+ } else { -+ print "LOG: $prio "; -+ print(@args); -+ print "\n"; -+ } -+} -+ -+logger(LOGDEBUG,Dumper(\@::dirs)); -+ -+ -+my $inotify = new Linux::Inotify2 or ( logger(LOGERR, "Unable to create new inotify object: $!") && die("inotify") ); -+ -+# For stats -+my $globaldirs=0; -+my $globalevents=0; -+my $globalhintretry=0; -+ -+sub logstatsline { -+ my ($file,$line)=@_; -+# open STATS,"> $file"; -+# print STATS $line; -+# close STATS; -+} -+ -+ -+#package Runner; -+################################################################################ -+# Process runner -+# Runs processes and keep status -+# API: -+# runstatus: current status of a runslot (running/idle) -+# exitstatus: last status of an exec -+# slotrun: forkexec a new command with a callback when it's finished for a specific slot -+# Helpers: -+# reaper is the SIGCHLD handler -+# checkchildren should be called after syscalls which exited with E_INTR, and -+# calls the specific callbacks. -+################################################################################ -+use constant { RUN_IDLE => 0, RUN_RUNNING => 1, RUN_REAPED =>2 }; -+my %slotstatus; -+my %slotexitstatus; -+my %slotcommandline; -+my %slotcallback; -+my %slotpid2slot; -+my %slotstarttime; -+ -+# pid queue for reaper -+# Every pid (key) contains a waitforpid exit status as value. -+my %slotpidreaped; -+ -+sub runstatus { -+ my ($slot)=@_; -+ return($slotstatus{$slot}) if exists($slotstatus{$slot}); -+ return RUN_IDLE; -+} -+sub slotrun { -+ my ($slot,$callback,$commandline)=(@_); -+ $SIG{CHLD} = \&reaper; -+ if(runstatus($slot)!=RUN_IDLE) { -+ logger(LOGDEBUG,"SlotRun: Asked to run for $slot, but $slot != RUN_IDLE"); -+ return -1; -+ } -+ $slotcommandline{$slot}=$commandline; -+ $slotcallback{$slot}=$callback; -+ $slotstatus{$slot}=RUN_RUNNING; -+ $slotstarttime{$slot}=time(); -+ my $pid=fork(); -+ if(!$pid) { -+ # We know that exec should not return. Now tell the perl interpreter that we know. -+ { -+ exec(@$commandline); -+ } -+ logger(LOGWARN,"SlotRun: $slot Exec failed: ".join(' ','>', @$commandline,'<')); -+ # If we can't exec, we don't really know why, and we don't want to go busy fork execing -+ # Give a fork exec grace by waiting -+ sleep 1; -+ exit 1; -+ } -+ logger(LOGDEBUG,"SlotRun: $slot # ".$pid.": run".join(' ','>', @$commandline,'<')); -+ $slotpid2slot{$pid}=$slot; -+} -+sub exitstatus { -+ my ($slot)=@_; -+ return($slotexitstatus{$slot}) if exists($slotexitstatus{$slot}); -+ return -1; -+} -+sub reaper { -+} -+ -+sub checkchildren { -+ if($::debug==LOGSLOTS) { -+ while(my ($slot,$status) = each %slotstatus) { -+ logger(LOGDEBUG,"SlotRun: $slot status $status time: ".($status?(time()-$slotstarttime{$slot}):'x')); -+ }; -+ } -+ while() { -+ my ($pid)=waitpid(-1,&WNOHANG); -+ if($pid<=0) { -+ last; -+ } -+ my $status=$?; -+ if (WIFEXITED($status)||WIFSIGNALED($status) && exists($slotpid2slot{$pid})) { -+ my $slot=$slotpid2slot{$pid}; -+ delete($slotpid2slot{$pid}); -+ $slotstatus{$slot}=RUN_IDLE; -+ $slotexitstatus{$slot}=$status; -+ logger(LOGDEBUG, "SlotRun: $slot $pid exited with $status == ".WEXITSTATUS($status).".\n"); -+ # Callback determines if we run again or not. -+ $slotcallback{$slot}->($slot,$slotexitstatus{$slot},$slotcommandline{$slot}); -+ } else { -+ logger(LOGDEBUG, "SlotRun: Unknown process $pid change state.\n"); -+ } -+ } -+} -+ -+ -+ -+ -+ -+ -+################################################################################ -+# CSYNC RUNNERS -+# groups queued hints into single csync commands -+# run csync update and check commands -+################################################################################ -+ -+# use constant { CSYNCHINT => 0 , CSYNCCHECK=>1 , CSYNCUPDATE=>2 }; -+my @hintfifo; -+ -+sub updateCallback { -+ my ($slot,$exitstatus,$command)=@_; -+ if($exitstatus) { -+ logger(LOGWARN,"Updater got ".$exitstatus.", NOT retrying run:".join(' ','>',@$command,'<')); -+ } -+} -+sub runupdater { -+ if(runstatus('csupdate') == RUN_IDLE) { -+ slotrun('csupdate',\&updateCallback,\@::csyncupdate); -+ } -+} -+ -+sub checkerCallback { -+ my ($slot,$exitstatus,$command)=@_; -+ if($exitstatus) { -+ logger(LOGWARN,"Checker got ".$exitstatus.", NOT retrying run:".join(' ','>',@$command,'<')); -+ } -+ runupdater(); -+} -+sub runchecker { -+ if(runstatus('cscheck') == RUN_IDLE) { -+ slotrun('cscheck',\&checkerCallback,\@::csynccheck); -+ } -+} -+sub hinterCallback { -+ my ($slot,$exitstatus,$command)=@_; -+ if($exitstatus) { -+ logger(LOGWARN,"Hinter got ".$exitstatus.", retrying run:".join(' ','>',@$command,'<')); -+ $globalhintretry++; -+ logstatsline($::statsretry,$globalhintretry); -+ slotrun($slot,\&hinterCallback,$command); -+ } else { -+ runchecker(); -+ } -+} -+sub givehints { -+ if(runstatus('cshint') == RUN_IDLE && @hintfifo) { -+ # PREPARE JOB -+ # Directories should be treated with care, one at a time. -+ my @hintcommand; -+ if($hintfifo[0]->{'recurse'}) { -+ my $filename=$hintfifo[0]->{'filename'}; -+ @hintcommand=(@::csyncdirhint,$filename); -+ shift(@hintfifo) while (@hintfifo && $filename eq $hintfifo[0]->{'filename'} ); -+ } else { -+ # Files can be bulked, until the next directory -+ my $nrargs=0; -+ @hintcommand=(@::csyncfilehint); -+ while($nrargs < $::csynchintmaxargs && @hintfifo && !$hintfifo[0]->{'recurse'}) { -+ my $filename=$hintfifo[0]->{'filename'}; -+ push(@hintcommand,$filename); -+ shift(@hintfifo) while (@hintfifo && $filename eq $hintfifo[0]->{'filename'} ); -+ $nrargs++; -+ } -+ } -+ slotrun('cshint',\&hinterCallback,\@hintcommand); -+ } -+} -+ -+################################################################################ -+# Subtree parser -+# Adds subtrees to an existing watch -+# globals: $globaldirs for stats. -+# Logs to logger -+################################################################################ -+sub watchtree { -+ my ($inotifier,$tree,$inotifyflags) = @_; -+ $inotifier->watch ($tree, $inotifyflags); -+ $globaldirs++; -+ find( -+ sub { -+ if(! m/^\.\.?$/) { -+ my ($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_) ; -+ if(-d _ ) { -+ if ($nlink==2) { -+ $File::Find::prune = 1; -+ } -+ $inotifier->watch ($File::Find::dir.'/'.$_, $inotifyflags) or die("WatchTree: watch creation failed (maybe increase the number of watches?)"); -+ $globaldirs++; -+ logger(LOGDEBUG,"WatchTree: directory ". $globaldirs." ".$File::Find::dir.'/'.$_); -+ } -+ } -+ }, -+ $tree -+ ); -+ logstatsline($::statsdir,$globaldirs); -+} -+ -+ -+################################################################################ -+# Main -+# -+logger(LOGINFO, 'Main: Starting $Id: csync2id.pl,v 1.18 2008/12/24 15:34:19 ard Exp $'); -+# Start watching the directories -+logger(LOGINFO, "Main: traversing directories"); -+eval { -+ watchtree($inotify,$_,IN_MOVE|IN_DELETE|IN_CLOSE_WRITE|IN_ATTRIB|IN_CREATE) foreach(@::dirs) -+}; -+if($@) { -+ logger(LOGERR,"Main: $@"); -+ exit(2); -+} -+logger(LOGINFO,"Main: ready for events"); -+ -+# Kill other daemon because we are ready -+if($daemonize) { -+ if ( -e $pidfile ) { -+ my $thepid; -+ @ARGV=($pidfile); -+ $thepid=<>; -+ logger(LOGINFO, "Main: about to kill previous incarnation $thepid"); -+ kill(15,$thepid); -+ sleep 0.5; -+ } -+ rename($pidfileboot,$pidfile); -+} -+ -+# Main loop -+$inotify->blocking(O_NONBLOCK); -+my $timeout=20; -+while () { -+ #my ($rhset,$dummy,$dummy,$timeleft)=IO::Select->select($selectset, undef, undef, 60); -+ my $nfound; -+ my $rin=''; -+ vec($rin,$inotify->fileno,1)=1; -+ ($nfound,$timeout)=select($rin, undef, undef, $timeout); -+ logger(LOGDEBUG,"Main: nrfds: $nfound timeleft: $timeout\n"); -+ if(!$timeout) { -+ $timeout=20; -+ logger(LOGDEBUG, "Main: timeout->check and update"); -+ runchecker(); -+ runupdater(); -+ # -+ } -+ if($nfound>0) { -+ my @events = $inotify->read; -+ unless (@events > 0) { -+ logger(LOGWARN,"Main: Zero events, must be a something weird"); -+ } -+ foreach(@events) { -+ if($_->IN_Q_OVERFLOW) { -+ logger(LOGERR,"Main: FATAL:inotify queue overflow: csync2id was to slow to handle events"); -+ } -+ if( $_->IN_ISDIR) { -+ my $recurse=0; -+ # We want to recurse only for new, renamed or deleted directories -+ $recurse=$_->IN_DELETE||$_->IN_CREATE||$_->IN_MOVED_TO||$_->IN_MOVED_FROM; -+ eval watchtree($inotify,$_->fullname,IN_MOVE|IN_DELETE|IN_CLOSE_WRITE|IN_ATTRIB|IN_CREATE) if $_->IN_CREATE||$_->IN_MOVED_TO; -+ if($@) { -+ logger(LOGINFO,"$@"); -+ exit(3); -+ } -+ push(@hintfifo,{ "filename" => $_->fullname , "recurse" => $recurse }); -+ logger(LOGDEBUG,"Main: dir: ".$_->mask." ".$recurse." ".$_->fullname); -+ } else { -+ # Accumulate single file events: -+ next if(@hintfifo && $hintfifo[-1]->{"filename"} eq $_->fullname); -+ push(@hintfifo,{ "filename" => $_->fullname , "recurse" => 0 }); -+ logger(LOGDEBUG,"Main: file: ".$_->mask," ".$_->fullname); -+ } -+ $globalevents++; -+ } -+ } -+ checkchildren(); -+ givehints(); -+ logstatsline($::statschanges,$globalevents); -+} -diff --git a/copycheck.sh b/copycheck.sh -new file mode 100755 -index 0000000..d4fe7d5 ---- /dev/null -+++ b/copycheck.sh -@@ -0,0 +1,37 @@ -+#!/bin/bash -+ -+errors=0 -+ignrev="r364" -+ -+check() { -+ if ! svn st $1 | grep -q '^?'; then -+ years="2003 2004 2005 2006 2007 2008" -+ for y in `svn log $1 | grep '^r[0-9]' | egrep -v "^($ignrev)" | sed 's,.* \(200.\)-.*,\1,' | sort -u` -+ do -+ years=`echo $years | sed "s,$y,,"` -+ if ! grep -q "\*.*Copyright.*$y" $1; then -+ echo "Missing $y in $1." -+ (( errors++ )) -+ fi -+ done -+ for y in $years -+ do -+ if grep -q "\*.*Copyright.*$y" $1; then -+ echo "Bogus $y in $1." -+ (( errors++ )) -+ fi -+ done -+ fi -+} -+ -+for f in `grep -rl '\*.*Copyright' . | grep -v '/\.svn/'` ; do -+ check $f -+done -+ -+if [ $errors -ne 0 ]; then -+ echo "Found $errors errors." -+ exit 1 -+fi -+ -+exit 0 -+ -diff --git a/csync2-postgres.sql b/csync2-postgres.sql -new file mode 100644 -index 0000000..c8975c1 ---- /dev/null -+++ b/csync2-postgres.sql -@@ -0,0 +1,56 @@ -+-- -+-- Table structure for table action -+-- -+ -+DROP TABLE IF EXISTS action; -+CREATE TABLE action ( -+ filename varchar(255) DEFAULT NULL, -+ command text, -+ logfile text, -+ UNIQUE (filename,command) -+); -+ -+-- -+-- Table structure for table dirty -+-- -+ -+DROP TABLE IF EXISTS dirty; -+CREATE TABLE dirty ( -+ filename varchar(200) DEFAULT NULL, -+ forced int DEFAULT NULL, -+ myname varchar(100) DEFAULT NULL, -+ peername varchar(100) DEFAULT NULL, -+ UNIQUE (filename,peername) -+); -+ -+-- -+-- Table structure for table file -+-- -+ -+DROP TABLE IF EXISTS file; -+CREATE TABLE file ( -+ filename varchar(200) DEFAULT NULL, -+ checktxt varchar(200) DEFAULT NULL, -+ UNIQUE (filename) -+); -+ -+-- -+-- Table structure for table hint -+-- -+ -+DROP TABLE IF EXISTS hint; -+CREATE TABLE hint ( -+ filename varchar(255) DEFAULT NULL, -+ recursive int DEFAULT NULL -+); -+ -+-- -+-- Table structure for table x509_cert -+-- -+ -+DROP TABLE IF EXISTS x509_cert; -+CREATE TABLE x509_cert ( -+ peername varchar(255) DEFAULT NULL, -+ certdata varchar(255) DEFAULT NULL, -+ UNIQUE (peername) -+); -diff --git a/csync2.c b/csync2.c -index 88fefa2..889be05 100644 ---- a/csync2.c -+++ b/csync2.c -@@ -36,18 +36,25 @@ - #include <errno.h> - #include <signal.h> - #include <ctype.h> -+#include <syslog.h> -+#include "db_api.h" -+#include <netdb.h> - - #ifdef REAL_DBDIR - # undef DBDIR - # define DBDIR REAL_DBDIR - #endif - --static char *file_database = 0; -+char *csync_database = 0; -+ -+int db_type = DB_SQLITE3; -+ - static char *file_config = 0; - static char *dbdir = DBDIR; - char *cfgname = ""; - - char myhostname[256] = ""; -+char *csync_port = "30865"; - char *active_grouplist = 0; - char *active_peerlist = 0; - -@@ -57,11 +64,11 @@ extern FILE *yyin; - int csync_error_count = 0; - int csync_debug_level = 0; - FILE *csync_debug_out = 0; -+int csync_syslog = 0; - - int csync_server_child_pid = 0; - int csync_timestamps = 0; - int csync_new_force = 0; --int csync_port = 30865; - - int csync_dump_dir_fd = -1; - -@@ -93,6 +100,11 @@ void help(char *cmd) - PACKAGE_STRING " - cluster synchronization tool, 2nd generation\n" - "LINBIT Information Technologies GmbH <http://www.linbit.com>\n" - "Copyright (C) 2004, 2005 Clifford Wolf <clifford@clifford.at>\n" -+"Copyright (C) 2010 Dennis Schafroth <dennis@schafroth.com>\n" -+"Copyright (C) 2010 Johannes Thoma <johannes.thoma@gmx.at>\n" -+"\n" -+"Version: " CSYNC2_VERSION "\n" -+"\n" - "This program is free software under the terms of the GNU GPL.\n" - "\n" - "Usage: %s [-v..] [-C config-name] \\\n" -@@ -103,7 +115,7 @@ PACKAGE_STRING " - cluster synchronization tool, 2nd generation\n" - " -c [-r] file.. Check files and maybe add to dirty db\n" - " -u [-d] [-r] file.. Updates files if listed in dirty db\n" - " -o [-r] file.. Create list of files in compare-mode\n" --" -f [-r] file.. Force this file in sync (resolve conflict)\n" -+" -f [-r] file.. Force files to win next conflict resolution\n" - " -m file.. Mark files in database as dirty\n" - "\n" - "Simple mode:\n" -@@ -161,11 +173,11 @@ PACKAGE_STRING " - cluster synchronization tool, 2nd generation\n" - " -U Don't mark all other peers as dirty when doing a -TI run.\n" - "\n" - " -G Group1,Group2,Group3,...\n" --" Only use this groups from config-file.\n" -+" Only use these groups from config-file.\n" - "\n" - " -P peer1,peer1,...\n" --" Only update this peers (still mark all as dirty).\n" --" Only show files for this peers in -o (compare) mode.\n" -+" Only update these peers (still mark all as dirty).\n" -+" Only show files for these peers in -o (compare) mode.\n" - "\n" - " -F Add new entries to dirty database with force flag set.\n" - "\n" -@@ -178,6 +190,15 @@ PACKAGE_STRING " - cluster synchronization tool, 2nd generation\n" - " found to the specified file descriptor (when doing a -c run).\n" - " The directory names in this output are zero-terminated.\n" - "\n" -+"Database switches:\n" -+"\n" -+" -D database-dir\n" -+" Use sqlite database in database dir (default: /var/lib/csync2)\n" -+"\n" -+" -a mysql-url\n" -+" Use mysql database in URL:\n" -+" mysql://[<user>:<password>@]<hostname>/<database>\n" -+"\n" - "Creating key file:\n" - " %s -k filename\n" - "\n" -@@ -190,69 +211,124 @@ PACKAGE_STRING " - cluster synchronization tool, 2nd generation\n" - int create_keyfile(const char *filename) - { - int fd = open(filename, O_WRONLY|O_CREAT|O_EXCL, 0600); -- int rand = open("/dev/random", O_RDONLY); -+ int rand = open("/dev/urandom", O_RDONLY); - char matrix[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"; - unsigned char n; - int i; -- -+ int rc; - assert(sizeof(matrix) == 65); - if ( fd == -1 ) { - fprintf(stderr, "Can't create key file: %s\n", strerror(errno)); - return 1; - } - if ( rand == -1 ) { -- fprintf(stderr, "Can't open /dev/random: %s\n", strerror(errno)); -+ fprintf(stderr, "Can't open /dev/urandom: %s\n", strerror(errno)); - return 1; - } - for (i=0; i<64; i++) { -- read(rand, &n, 1); -- write(fd, matrix+(n&63), 1); -+ rc = read(rand, &n, 1); -+ rc = write(fd, matrix+(n&63), 1); - } -- write(fd, "\n", 1); -+ rc = write(fd, "\n", 1); - close(rand); - close(fd); - return 0; - } - --static int csync_server_loop(int single_connect) -+static int csync_server_bind(void) - { - struct linger sl = { 1, 5 }; -- struct sockaddr_in addr; -- int on = 1; -+ struct addrinfo hints; -+ struct addrinfo *result, *rp; -+ int save_errno; -+ int sfd, s, on = 1; -+ memset(&hints, 0, sizeof(struct addrinfo)); -+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ -+ hints.ai_socktype = SOCK_STREAM; -+ hints.ai_flags = AI_PASSIVE; -+ -+ s = getaddrinfo(NULL, csync_port, &hints, &result); -+ if (s != 0) { -+ csync_debug(1, "Cannot prepare local socket, getaddrinfo: %s\n", gai_strerror(s)); -+ return -1; -+ } - -- int listenfd = socket(AF_INET, SOCK_STREAM, 0); -- if (listenfd < 0) goto error; -+ /* getaddrinfo() returns a list of address structures. -+ Try each address until we successfully bind(2). -+ If socket(2) (or bind(2)) fails, we (close the socket -+ and) try the next address. */ -+ -+ for (rp = result; rp != NULL; rp = rp->ai_next) { -+ sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); -+ if (sfd == -1) -+ continue; -+ -+ if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, (socklen_t) sizeof(on)) < 0) -+ goto error; -+ if (setsockopt(sfd, SOL_SOCKET, SO_LINGER, &sl, (socklen_t) sizeof(sl)) < 0) -+ goto error; -+ if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &on, (socklen_t) sizeof(on)) < 0) -+ goto error; - -- bzero(&addr, sizeof(addr)); -- addr.sin_family = AF_INET; -- addr.sin_addr.s_addr = htonl(INADDR_ANY); -- addr.sin_port = htons(csync_port); -+ if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) -+ break; /* Success */ -+ -+ close(sfd); -+ } - -- if ( setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, (socklen_t) sizeof(on)) < 0 ) goto error; -- if ( setsockopt(listenfd, SOL_SOCKET, SO_LINGER, &sl, (socklen_t) sizeof(sl)) < 0 ) goto error; -- if ( setsockopt(listenfd, IPPROTO_TCP, TCP_NODELAY, &on, (socklen_t) sizeof(on)) < 0 ) goto error; -+ freeaddrinfo(result); /* No longer needed */ - -- if ( bind(listenfd, (struct sockaddr *) &addr, sizeof(addr)) < 0 ) goto error; -- if ( listen(listenfd, 5) < 0 ) goto error; -+ if (rp == NULL) /* No address succeeded */ -+ return -1; -+ -+ return sfd; -+ -+error: -+ save_errno = errno; -+ close(sfd); -+ errno = save_errno; -+ return -1; -+} - -+static int csync_server_loop(int single_connect) -+{ -+ union { -+ struct sockaddr sa; -+ struct sockaddr_in sa_in; -+ struct sockaddr_in6 sa_in6; -+ struct sockaddr_storage ss; -+ } addr; -+ int listenfd = csync_server_bind(); -+ if (listenfd < 0) goto error; -+ -+ if (listen(listenfd, 5) < 0) goto error; -+ -+ /* we want to "cleanly" shutdown if the connection is lost unexpectedly */ - signal(SIGPIPE, SIG_IGN); -+ /* server is not interested in its childs, prevent zombies */ - signal(SIGCHLD, SIG_IGN); - - printf("Csync2 daemon running. Waiting for connections.\n"); - - while (1) { -- int addrlen = sizeof(addr); -- int conn = accept(listenfd, (struct sockaddr *) &addr, &addrlen); -+ unsigned addrlen = sizeof(addr); -+ int conn = accept(listenfd, &addr.sa, &addrlen); - if (conn < 0) goto error; - - fflush(stdout); fflush(stderr); - - if (single_connect || !fork()) { -+ char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; -+ /* need to restore default SIGCHLD handler in the session, -+ * as we may need to wait on them in action.c */ -+ signal(SIGCHLD, SIG_DFL); - csync_server_child_pid = getpid(); -- fprintf(stderr, "<%d> New connection from %s:%u.\n", -- csync_server_child_pid, -- inet_ntoa(addr.sin_addr), -- ntohs(addr.sin_port)); -+ if (getnameinfo(&addr.sa, addrlen, -+ hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), -+ NI_NUMERICHOST | NI_NUMERICSERV) != 0) -+ goto error; -+ fprintf(stderr, "<%d> New connection from %s:%s.\n", -+ csync_server_child_pid, hbuf, sbuf); - fflush(stderr); - - dup2(conn, 0); -@@ -293,8 +369,13 @@ int main(int argc, char ** argv) - return 1; - } - -- while ( (opt = getopt(argc, argv, "W:s:Ftp:G:P:C:D:N:HBAIXULSTMRvhcuoimfxrd")) != -1 ) { -+ while ( (opt = getopt(argc, argv, "a:W:s:Ftp:G:P:C:D:N:HBAIXULlSTMRvhcuoimfxrd")) != -1 ) { -+ - switch (opt) { -+ case 'a': -+ csync_database = optarg; -+ db_type = DB_MYSQL; -+ break; - case 'W': - csync_dump_dir_fd = atoi(optarg); - if (write(csync_dump_dir_fd, 0, 0) < 0) -@@ -314,7 +395,7 @@ int main(int argc, char ** argv) - csync_timestamps = 1; - break; - case 'p': -- csync_port = atoi(optarg); -+ csync_port = strdup(optarg); - break; - case 'G': - active_grouplist = optarg; -@@ -349,6 +430,10 @@ int main(int argc, char ** argv) - case 'v': - csync_debug_level++; - break; -+ case 'l': -+ csync_syslog = 1; -+ openlog("csync2", LOG_ODELAY, LOG_LOCAL0); -+ break; - case 'h': - if ( mode != MODE_NONE ) help(argv[0]); - mode = MODE_HINT; -@@ -450,6 +535,13 @@ int main(int argc, char ** argv) - if ( mode == MODE_NONE ) - help(argv[0]); - -+ /* Some inetd connect stderr to stdout. The debug level messages on -+ * stderr would confuse the csync2 protocol. Log to syslog instead. */ -+ if ( mode == MODE_INETD && csync_debug_level && !csync_syslog ) { -+ csync_syslog = 1; -+ openlog("csync2", LOG_ODELAY, LOG_LOCAL0); -+ } -+ - if ( *myhostname == 0 ) { - gethostname(myhostname, 256); - myhostname[255] = 0; -@@ -482,7 +574,7 @@ int main(int argc, char ** argv) - para = cmd ? strtok(0, "\t \r\n") : 0; - - if (cmd && !strcasecmp(cmd, "ssl")) { --#ifdef HAVE_LIBGNUTLS_OPENSSL -+#ifdef HAVE_LIBGNUTLS - conn_printf("OK (activating_ssl).\n"); - conn_activate_ssl(1); - -@@ -503,10 +595,8 @@ int main(int argc, char ** argv) - if (para) - cfgname = strdup(url_decode(para)); - } -- - if ( !*cfgname ) { -- asprintf(&file_database, "%s/%s.db", dbdir, myhostname); -- asprintf(&file_config, ETCDIR "/csync2.cfg"); -+ ASPRINTF(&file_config, ETCDIR "/csync2.cfg"); - } else { - int i; - -@@ -518,14 +608,10 @@ int main(int argc, char ** argv) - return mode != MODE_INETD; - } - -- asprintf(&file_database, "%s/%s_%s.db", dbdir, myhostname, cfgname); -- asprintf(&file_config, ETCDIR "/csync2_%s.cfg", cfgname); -+ ASPRINTF(&file_config, ETCDIR "/csync2_%s.cfg", cfgname); - } - -- csync_debug(2, "My hostname is %s.\n", myhostname); -- csync_debug(2, "Database-File: %s\n", file_database); - csync_debug(2, "Config-File: %s\n", file_config); -- - yyin = fopen(file_config, "r"); - if ( !yyin ) - csync_fatal("Can not open config file `%s': %s\n", -@@ -533,6 +619,12 @@ int main(int argc, char ** argv) - yyparse(); - fclose(yyin); - -+ if (!csync_database) -+ csync_database = db_default_database(dbdir, myhostname, cfgname); -+ -+ csync_debug(2, "My hostname is %s.\n", myhostname); -+ csync_debug(2, "Database-File: %s\n", csync_database); -+ - { - const struct csync_group *g; - for (g=csync_group; g; g=g->next) -@@ -541,7 +633,7 @@ int main(int argc, char ** argv) - found_a_group:; - } - -- csync_db_open(file_database); -+ csync_db_open(csync_database); - - for (i=optind; i < argc; i++) - on_cygwin_lowercase(argv[i]); -@@ -582,8 +674,8 @@ found_a_group:; - SQL_BEGIN("Check all hints", - "SELECT filename, recursive FROM hint") - { -- textlist_add(&tl, url_decode(SQL_V[0]), -- atoi(SQL_V[1])); -+ textlist_add(&tl, url_decode(SQL_V(0)), -+ atoi(SQL_V(1))); - } SQL_END; - - for (t = tl; t != 0; t = t->next) { -@@ -642,51 +734,56 @@ found_a_group:; - case MODE_MARK: - for (i=optind; i < argc; i++) { - char *realname = getrealfn(argv[i]); -+ char *pfname; - csync_check_usefullness(realname, recursive); -- csync_mark(realname, 0, 0); -+ pfname=strdup(prefixencode(realname)); -+ csync_mark(pfname, 0, 0); - - if ( recursive ) { - char *where_rec = ""; - - if ( !strcmp(realname, "/") ) -- asprintf(&where_rec, "or 1"); -+ ASPRINTF(&where_rec, "or 1=1"); - else -- asprintf(&where_rec, "or (filename > '%s/' " -- "and filename < '%s0')", -- url_encode(realname), url_encode(realname)); -+ ASPRINTF(&where_rec, "UNION ALL SELECT filename from file where filename > '%s/' " -+ "and filename < '%s0'", -+ url_encode(pfname), url_encode(pfname)); - - SQL_BEGIN("Adding dirty entries recursively", - "SELECT filename FROM file WHERE filename = '%s' %s", -- url_encode(realname), where_rec) -+ url_encode(pfname), where_rec) - { -- char *filename = strdup(url_encode(SQL_V[0])); -+ char *filename = strdup(url_decode(SQL_V(0))); - csync_mark(filename, 0, 0); - free(filename); - } SQL_END; - } -+ free(pfname); - } - break; - - case MODE_FORCE: - for (i=optind; i < argc; i++) { - char *realname = getrealfn(argv[i]); -+ char *pfname = strdup(prefixencode(realname)); - char *where_rec = ""; - - if ( recursive ) { - if ( !strcmp(realname, "/") ) -- asprintf(&where_rec, "or 1"); -+ ASPRINTF(&where_rec, "or 1=1"); - else -- asprintf(&where_rec, "or (filename > '%s/' " -+ ASPRINTF(&where_rec, "or (filename > '%s/' " - "and filename < '%s0')", - url_encode(realname), url_encode(realname)); - } - - SQL("Mark file as to be forced", -- "UPDATE dirty SET force = 1 WHERE filename = '%s' %s", -+ "UPDATE dirty SET forced = 1 WHERE filename = '%s' %s", - url_encode(realname), where_rec); - - if ( recursive ) - free(where_rec); -+ free(pfname); - } - break; - -@@ -695,7 +792,7 @@ found_a_group:; - SQL_BEGIN("DB Dump - Hint", - "SELECT recursive, filename FROM hint ORDER BY filename") - { -- printf("%s\t%s\n", SQL_V[0], url_decode(SQL_V[1])); -+ printf("%s\t%s\n", (char*)SQL_V(0), url_decode(SQL_V(1))); - retval = -1; - } SQL_END; - break; -@@ -705,8 +802,8 @@ found_a_group:; - SQL_BEGIN("DB Dump - File", - "SELECT checktxt, filename FROM file ORDER BY filename") - { -- if (csync_find_next(0, url_decode(SQL_V[1]))) { -- printf("%s\t%s\n", url_decode(SQL_V[0]), url_decode(SQL_V[1])); -+ if (csync_find_next(0, url_decode(SQL_V(1)))) { -+ printf("%s\t%s\n", url_decode(SQL_V(0)), url_decode(SQL_V(1))); - retval = -1; - } - } SQL_END; -@@ -717,8 +814,8 @@ found_a_group:; - SQL_BEGIN("DB Dump - File", - "SELECT checktxt, filename FROM file ORDER BY filename") - { -- if ( csync_match_file_host(url_decode(SQL_V[1]), argv[optind], argv[optind+1], 0) ) { -- printf("%s\t%s\n", url_decode(SQL_V[0]), url_decode(SQL_V[1])); -+ if ( csync_match_file_host(url_decode(SQL_V(1)), argv[optind], argv[optind+1], 0) ) { -+ printf("%s\t%s\n", url_decode(SQL_V(0)), url_decode(SQL_V(1))); - retval = -1; - } - } SQL_END; -@@ -767,11 +864,11 @@ found_a_group:; - case MODE_LIST_DIRTY: - retval = 2; - SQL_BEGIN("DB Dump - Dirty", -- "SELECT force, myname, peername, filename FROM dirty ORDER BY filename") -+ "SELECT forced, myname, peername, filename FROM dirty ORDER BY filename") - { -- if (csync_find_next(0, url_decode(SQL_V[3]))) { -- printf("%s\t%s\t%s\t%s\n", atoi(SQL_V[0]) ? "force" : "chary", -- url_decode(SQL_V[1]), url_decode(SQL_V[2]), url_decode(SQL_V[3])); -+ if (csync_find_next(0, url_decode(SQL_V(3)))) { -+ printf("%s\t%s\t%s\t%s\n", atoi(SQL_V(0)) ? "force" : "chary", -+ url_decode(SQL_V(1)), url_decode(SQL_V(2)), url_decode(SQL_V(3))); - retval = -1; - } - } SQL_END; -diff --git a/csync2.cfg b/csync2.cfg -index 338bb7b..ff9e639 100644 ---- a/csync2.cfg -+++ b/csync2.cfg -@@ -1,4 +1,3 @@ -- - # Csync2 Example Configuration File - # --------------------------------- - # -@@ -12,6 +11,22 @@ - # - # key /etc/csync2.key_mygroup; - # -+# # -+# # WARNING: -+# # You CANNOT use paths containing a symlink -+# # component in include/exclude options! -+# # -+# # Here is a real-life example: -+# # Suppose you have some 64bit Linux systems -+# # and /usr/lib/ocf is what you want to keep -+# # in sync. On 64bit Linux systems, /usr/lib -+# # is usually a symlink to /usr/lib64. -+# # This does not work: -+# # include /usr/lib/ocf; -+# # But this does work: -+# # include /usr/lib64/ocf; -+# # -+# - # include /etc/apache; - # include %homedir%/bob; - # exclude %homedir%/bob/temp; -@@ -24,8 +39,12 @@ - # exec "/usr/sbin/apache2ctl graceful"; - # logfile "/var/log/csync2_action.log"; - # do-local; -+# # you can use do-local-only if the execution -+# # should be done locally only -+# # do-local-only; - # } - # -+# # The backup-directory needs to be created first! - # backup-directory /var/backups/csync2; - # backup-generations 3; - # -@@ -37,4 +56,3 @@ - # on host[12]: /export/users; - # on *: /home; - # } -- -diff --git a/csync2.h b/csync2.h -index 1306023..d76f880 100644 ---- a/csync2.h -+++ b/csync2.h -@@ -21,7 +21,11 @@ - #ifndef CSYNC2_H - #define CSYNC2_H 1 - -+#define CSYNC2_VERSION "2.0-rc1" -+ -+#ifndef _GNU_SOURCE - #define _GNU_SOURCE -+#endif - - #include "config.h" - #include <stdio.h> -@@ -31,6 +35,24 @@ - #include <errno.h> - - -+#define DB_SCHEMA_VERSION 0 -+ -+/* asprintf with test for no memory */ -+ -+#define ASPRINTF(s, fmt, ...) do {\ -+ int __ret = asprintf(s, fmt, ##__VA_ARGS__);\ -+ if (__ret < 0) \ -+ csync_fatal("Out of memory in asprintf at %s:%d\n", __FILE__, __LINE__);\ -+} while (0) -+ -+ -+#define VASPRINTF(s, fmt, args) do {\ -+ int __ret = vasprintf(s, fmt, args);\ -+ if (__ret < 0) \ -+ csync_fatal("Out of memory in vasprintf at %s:%d\n", __FILE__, __LINE__);\ -+} while (0) -+ -+ - /* action.c */ - - extern void csync_schedule_commands(const char *filename, int islocal); -@@ -78,7 +100,7 @@ extern int conn_write(const void *buf, size_t count); - - extern void conn_printf(const char *fmt, ...); - extern int conn_fgets(char *s, int size); --extern int conn_gets(char *s, int size); -+extern size_t conn_gets(char *s, size_t size); - - - /* db.c */ -@@ -91,26 +113,56 @@ extern void* csync_db_begin(const char *err, const char *fmt, ...); - extern int csync_db_next(void *vmx, const char *err, - int *pN, const char ***pazValue, const char ***pazColName); - extern void csync_db_fin(void *vmx, const char *err); -+extern const void * csync_db_colblob(void *stmtx,int col); -+extern char *db_default_database(char *dbdir, char *myhostname, char *cfg_name); -+ - - #define SQL(e, s, ...) csync_db_sql(e, s, ##__VA_ARGS__) - -+#if 0 -+#if defined(HAVE_LIBSQLITE) - #define SQL_BEGIN(e, s, ...) \ - { \ - char *SQL_ERR = e; \ - void *SQL_VM = csync_db_begin(SQL_ERR, s, ##__VA_ARGS__); \ - int SQL_COUNT = 0; \ - while (1) { \ -- const char **SQL_V, **SQL_N; \ -+ const char **dataSQL_V, **dataSQL_N; \ - int SQL_C; \ - if ( !csync_db_next(SQL_VM, SQL_ERR, \ -- &SQL_C, &SQL_V, &SQL_N) ) break; \ -+ &SQL_C, &dataSQL_V, &dataSQL_N) ) break; \ - SQL_COUNT++; - -+#define SQL_V(col) \ -+ (dataSQL_V[(col)]) -+#endif -+#endif -+ -+// #if defined(HAVE_LIBSQLITE3) -+ -+#define SQL_BEGIN(e, s, ...) \ -+{ \ -+ char *SQL_ERR = e; \ -+ void *SQL_VM = csync_db_begin(SQL_ERR, s, ##__VA_ARGS__); \ -+ int SQL_COUNT = 0; \ -+\ -+ if (SQL_VM) { \ -+ while (1) { \ -+ const char **dataSQL_V, **dataSQL_N; \ -+ int SQL_C; \ -+ if ( !csync_db_next(SQL_VM, SQL_ERR, \ -+ &SQL_C, &dataSQL_V, &dataSQL_N) ) break; \ -+ SQL_COUNT++; -+ -+#define SQL_V(col) \ -+ (csync_db_colblob(SQL_VM,(col))) -+// #endif - #define SQL_FIN }{ - - #define SQL_END \ -+ } \ -+ csync_db_fin(SQL_VM, SQL_ERR); \ - } \ -- csync_db_fin(SQL_VM, SQL_ERR); \ - } - - extern int db_blocking_mode; -@@ -150,6 +202,7 @@ extern void csync_remove_old(); - /* daemon.c */ - - extern void csync_daemon_session(); -+extern int csync_copy_file(int fd_in, int fd_out); - - - /* getrealfn.c */ -@@ -170,6 +223,7 @@ const char *url_decode(const char *in); - - /* another ringbuffer here. so use it with care!! */ - const char *prefixsubst(const char *in); -+const char *prefixencode(const char *filename); - - - /* textlist implementation */ -@@ -233,12 +287,13 @@ struct csync_group_host { - - struct csync_group_pattern { - struct csync_group_pattern *next; -- int isinclude, iscompare; -+ int isinclude, iscompare, star_matches_slashes; - const char *pattern; - }; - - struct csync_group_action_pattern { - struct csync_group_action_pattern *next; -+ int star_matches_slashes; - const char *pattern; - }; - -@@ -253,6 +308,7 @@ struct csync_group_action { - struct csync_group_action_command *command; - const char *logfile; - int do_local; -+ int do_local_only; - }; - - struct csync_group { -@@ -301,8 +357,14 @@ extern struct csync_group *csync_group; - extern struct csync_prefix *csync_prefix; - extern struct csync_nossl *csync_nossl; - -+extern unsigned csync_lock_timeout; -+extern char *csync_tempdir; -+ -+extern char *csync_database; -+ - extern int csync_error_count; - extern int csync_debug_level; -+extern int csync_syslog; - extern FILE *csync_debug_out; - - extern long csync_last_printtime; -@@ -312,9 +374,9 @@ extern int csync_messages_printed; - extern int csync_server_child_pid; - extern int csync_timestamps; - extern int csync_new_force; --extern int csync_port; - - extern char myhostname[]; -+extern char *csync_port; - extern char *active_grouplist; - extern char *active_peerlist; - -@@ -328,7 +390,7 @@ extern int csync_dump_dir_fd; - - extern int csync_compare_mode; - --#ifdef HAVE_LIBGNUTLS_OPENSSL -+#ifdef HAVE_LIBGNUTLS - extern int csync_conn_usessl; - #endif - -diff --git a/csync2.spec b/csync2.spec -index 17daad6..5d342dc 100644 ---- a/csync2.spec -+++ b/csync2.spec -@@ -23,15 +23,15 @@ - # norootforbuild - # neededforbuild openssl openssl-devel - --BuildRequires: sqlite-devel sqlite librsync openssl-devel librsync-devel -+BuildRequires: sqlite-devel sqlite librsync gnutls-devel librsync-devel - - Name: csync2 - License: GPL - Group: System/Monitoring - Requires: sqlite openssl librsync - Autoreqprov: on --Version: 1.34 --Release: 1 -+Version: 2.0 -+Release: 0.1.rc1 - Source0: csync2-%{version}.tar.gz - URL: http://oss.linbit.com/csync2 - BuildRoot: %{_tmppath}/%{name}-%{version}-build -@@ -83,6 +83,7 @@ fi - %defattr(-,root,root) - %doc ChangeLog README NEWS INSTALL TODO AUTHORS - %{_sbindir}/csync2 -+%{_sbindir}/csync2-compare - %{_var}/lib/csync2 - %{_mandir}/man1/csync2.1.gz - %config(noreplace) %{_sysconfdir}/xinetd.d/csync2 -diff --git a/daemon.c b/daemon.c -index a6357fa..2c054ed 100644 ---- a/daemon.c -+++ b/daemon.c -@@ -23,6 +23,7 @@ - #include <sys/stat.h> - #include <sys/socket.h> - #include <netinet/in.h> -+#include <arpa/inet.h> - #include <string.h> - #include <fnmatch.h> - #include <stdlib.h> -@@ -38,6 +39,9 @@ - - static char *cmd_error; - -+int csync_setBackupFileStatus(char *filename, int backupDirLength); -+ -+ - int csync_unlink(const char *filename, int ign) - { - struct stat st; -@@ -80,8 +84,13 @@ void csync_file_update(const char *filename, const char *peername) - url_encode(filename)); - } else { - const char *checktxt = csync_genchecktxt(&st, filename, 0); -+ -+ SQL("Deleting old record from file db", -+ "DELETE FROM file WHERE filename = '%s'", -+ url_encode(filename)); -+ - SQL("Insert record to file db", -- "insert into file (filename, checktxt) values " -+ "INSERT INTO file (filename, checktxt) values " - "('%s', '%s')", url_encode(filename), - url_encode(checktxt)); - } -@@ -98,73 +107,164 @@ int csync_file_backup(const char *filename) - { - static char error_buffer[1024]; - const struct csync_group *g = NULL; -+ struct stat buf; -+ int rc; - while ( (g=csync_find_next(g, filename)) ) { -- if (g->backup_directory && g->backup_generations > 0) { -- int bak_dir_len = strlen(g->backup_directory); -- int filename_len = strlen(filename); -- char backup_filename[bak_dir_len + filename_len + 10]; -- char backup_otherfilename[bak_dir_len + filename_len + 10]; -- int fd_in, fd_out, i; -- -- fd_in = open(filename, O_RDONLY); -- if (fd_in < 0) return 0; -- -- memcpy(backup_filename, g->backup_directory, bak_dir_len); -- for (i=0; i<filename_len; i++) -- backup_filename[bak_dir_len+i] = -- filename[i] == '/' ? '_' : filename[i]; -- backup_filename[bak_dir_len] = '/'; -- memcpy(backup_otherfilename, backup_filename, -- bak_dir_len + filename_len); -- -- for (i=g->backup_generations-1; i; i--) { -- snprintf(backup_filename+bak_dir_len+filename_len, 10, ".%d", i-1); -- snprintf(backup_otherfilename+bak_dir_len+filename_len, 10, ".%d", i); -- rename(backup_filename, backup_otherfilename); -- } -- -- strcpy(backup_filename+bak_dir_len+filename_len, ".0"); -- fd_out = open(backup_filename, O_WRONLY|O_CREAT, 0600); -- -- if (fd_out < 0) { -- snprintf(error_buffer, 1024, -- "Open error while backing up '%s': %s\n", -- filename, strerror(errno)); -- cmd_error = error_buffer; -- close(fd_in); -- return 1; -- } -- -- while (1) { -- char buffer[512]; -- int read_len = read(fd_in, buffer, 512); -- int write_len = 0; -- -- if (read_len <= 0) -- break; -- -- while (write_len < read_len) { -- int rc = write(fd_out, buffer+write_len, read_len-write_len); -- if (rc <= 0) { -- snprintf(error_buffer, 1024, -- "Write error while backing up '%s': %s\n", -- filename, strerror(errno)); -- cmd_error = error_buffer; -- close(fd_in); -- close(fd_out); -- return 1; -- } -- write_len += rc; -- } -- } -- close(fd_in); -- close(fd_out); -- } -+ if (g->backup_directory && g->backup_generations > 1) { -+ -+ int bak_dir_len = strlen(g->backup_directory); -+ int filename_len = strlen(filename); -+ char backup_filename[bak_dir_len + filename_len + 10]; -+ char backup_otherfilename[bak_dir_len + filename_len + 10]; -+ int fd_in, fd_out, i; -+ int lastSlash = 0; -+ mode_t mode; -+ csync_debug(1, "backup\n"); -+ // Skip generation of directories -+ rc = stat(filename, &buf); -+ if (S_ISDIR(buf.st_mode)) { -+ csync_debug(1, "directory. Skip generation \n"); -+ return 0; -+ } -+ -+ fd_in = open(filename, O_RDONLY); -+ if (fd_in < 0) -+ return 0; -+ -+ memcpy(backup_filename, g->backup_directory, bak_dir_len); -+ backup_filename[bak_dir_len] = 0; -+ mode = 0777; -+ -+ -+ for (i=filename_len; i> 0; i--) -+ if (filename[i] == '/') { -+ lastSlash = i; -+ break; -+ } -+ -+ for (i=0; i < filename_len; i++) { -+ // Create directories in filename -+ // TODO: Get the mode from the orig. dir -+ if (filename[i] == '/' && i <= lastSlash) { -+ -+ backup_filename[bak_dir_len+i] = 0; -+ -+ csync_debug(1, "mkdir %s \n", backup_filename); -+ -+ mkdir(backup_filename, mode); -+ // Dont check the empty string. -+ if (i!= 0) -+ csync_setBackupFileStatus(backup_filename, bak_dir_len); -+ -+ } -+ backup_filename[bak_dir_len+i] = filename[i]; -+ } -+ -+ backup_filename[bak_dir_len + filename_len] = 0; -+ backup_filename[bak_dir_len] = '/'; -+ memcpy(backup_otherfilename, backup_filename, -+ bak_dir_len + filename_len); -+ -+ //rc = unlink( -+ for (i=g->backup_generations-1; i; i--) { -+ -+ if (i != 1) -+ snprintf(backup_filename+bak_dir_len+filename_len, 10, ".%d", i-1); -+ backup_filename[bak_dir_len+filename_len] = '\0'; -+ snprintf(backup_otherfilename+bak_dir_len+filename_len, 10, ".%d", i); -+ -+ rc = rename(backup_filename, backup_otherfilename); -+ csync_debug(1, "renaming backup files '%s' to '%s'. rc = %d\n", backup_filename, backup_otherfilename, rc); -+ -+ } -+ -+ /* strcpy(backup_filename+bak_dir_len+filename_len, ""); */ -+ -+ fd_out = open(backup_filename, O_WRONLY|O_CREAT, 0600); -+ -+ if (fd_out < 0) { -+ snprintf(error_buffer, 1024, -+ "Open error while backing up '%s': %s\n", -+ filename, strerror(errno)); -+ cmd_error = error_buffer; -+ close(fd_in); -+ return 1; -+ } -+ -+ csync_debug(1,"Copying data from %s to backup file %s \n", filename, backup_filename); -+ -+ rc = csync_copy_file(fd_in, fd_out); -+ if (rc != 0) { -+ csync_debug(1, "csync_backup error 2\n"); -+ -+ snprintf(error_buffer, 1024, -+ "Write error while backing up '%s': %s\n", -+ filename, strerror(errno)); -+ -+ cmd_error = error_buffer; -+ // TODO verify file disapeared ? -+ // -+ // return 1; -+ } -+ csync_setBackupFileStatus(backup_filename, bak_dir_len); -+ csync_debug(1, "csync_backup loop end\n"); -+ } - } -- -+ csync_debug(1, "csync_backup end\n"); - return 0; - } - -+int csync_copy_file(int fd_in, int fd_out) -+{ -+ char buffer[512]; -+ int read_len = read(fd_in, buffer, 512); -+ -+ while (read_len > 0) { -+ int write_len = 0; -+ -+ while (write_len < read_len) { -+ int rc = write(fd_out, buffer+write_len, read_len-write_len); -+ if (rc == -1) { -+ close(fd_in); -+ close(fd_out); -+ //TODO verify return code. -+ return errno; -+ } -+ write_len += rc; -+ } -+ read_len = read(fd_in, buffer, 512); -+ } -+ close(fd_in); -+ close(fd_out); -+ return 0; -+} -+ -+/* get the mode from the orig directory. -+ Looking from the back_dir_len should produce the original dir. -+*/ -+int csync_setBackupFileStatus(char *filename, int backupDirLength) { -+ -+ struct stat buf; -+ int rc = stat((filename + backupDirLength), &buf); -+ if (rc == 0 ) { -+ csync_debug(0, "Stating original file %s rc: %d mode: %o", (filename + backupDirLength), rc, buf.st_mode); -+ -+ rc = chown(filename, buf.st_uid, buf.st_gid); -+ csync_debug(0, "Changing owner of %s to user %d and group %d, rc= %d \n", -+ filename, buf.st_uid, buf.st_gid, rc); -+ -+ rc = chmod(filename, buf.st_mode); -+ csync_debug(0, "Changing mode of %s to mode %d, rc= %d \n", -+ filename, buf.st_mode, rc); -+ -+ } -+ else { -+ csync_debug(0, "Error getting mode and owner ship from %s \n", (filename + backupDirLength)); -+ return -1; -+ } -+ return 0; -+}; -+ - struct csync_command { - char *text; - int check_perm; -@@ -210,16 +310,149 @@ struct csync_command cmdtab[] = { - { 0, 0, 0, 0, 0, 0, 0 } - }; - -+typedef union address { -+ struct sockaddr sa; -+ struct sockaddr_in sa_in; -+ struct sockaddr_in6 sa_in6; -+ struct sockaddr_storage ss; -+} address_t; -+ -+const char *csync_inet_ntop(address_t *addr) -+{ -+ char buf[INET6_ADDRSTRLEN]; -+ sa_family_t af = addr->sa.sa_family; -+ return inet_ntop(af, -+ af == AF_INET ? (void*)&addr->sa_in.sin_addr : -+ af == AF_INET6 ? (void*)&addr->sa_in6.sin6_addr : NULL, -+ buf, sizeof(buf)); -+} -+ -+/* -+ * Loops (to cater for multihomed peers) through the address list returned by -+ * gethostbyname(), returns 1 if any match with the address obtained from -+ * getpeername() during session startup. -+ * Otherwise returns 0 (-> identification failed). -+ * -+ * TODO switch to a getnameinfo in conn_open. -+ * TODO add a "pre-authenticated" pipe mode for use over ssh */ -+int verify_peername(const char *name, address_t *peeraddr) -+{ -+ sa_family_t af = peeraddr->sa.sa_family; -+ struct addrinfo hints; -+ struct addrinfo *result, *rp; -+ int try_mapped_ipv4; -+ int s; -+ -+ /* Obtain address(es) matching host */ -+ memset(&hints, 0, sizeof(struct addrinfo)); -+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ -+ hints.ai_socktype = SOCK_STREAM; /* Datagram socket */ -+ -+ s = getaddrinfo(name, NULL, &hints, &result); -+ if (s != 0) { -+ csync_debug(1, "getaddrinfo: %s\n", gai_strerror(s)); -+ return 0; -+ } -+ -+ try_mapped_ipv4 = -+ af == AF_INET6 && -+ !memcmp(&peeraddr->sa_in6.sin6_addr, -+ "\0\0\0\0" "\0\0\0\0" "\0\0\xff\xff", 12); -+ -+ /* getaddrinfo() returns a list of address structures. -+ * Try each address. */ -+ -+ for (rp = result; rp != NULL; rp = rp->ai_next) { -+ /* both IPv4 */ -+ if (af == AF_INET && rp->ai_family == AF_INET && -+ !memcmp(&((struct sockaddr_in*)rp->ai_addr)->sin_addr, -+ &peeraddr->sa_in.sin_addr, sizeof(struct in_addr))) -+ break; -+ /* both IPv6 */ -+ if (af == AF_INET6 && rp->ai_family == AF_INET6 && -+ !memcmp(&((struct sockaddr_in6*)rp->ai_addr)->sin6_addr, -+ &peeraddr->sa_in6.sin6_addr, sizeof(struct in6_addr))) -+ break; -+ /* peeraddr IPv6, but actually ::ffff:I.P.v.4, -+ * and forward lookup returned IPv4 only */ -+ if (af == AF_INET6 && rp->ai_family == AF_INET && -+ try_mapped_ipv4 && -+ !memcmp(&((struct sockaddr_in*)rp->ai_addr)->sin_addr, -+ (unsigned char*)&peeraddr->sa_in6.sin6_addr + 12, -+ sizeof(struct in_addr))) -+ break; -+ } -+ freeaddrinfo(result); -+ if (rp != NULL) /* memcmp found a match */ -+ return conn_check_peer_cert(name, 0); -+ return 0; -+} -+ -+/* Why do all this fuzz, and not simply --assume-authenticated? -+ * To limit the impact of an accidental misconfiguration. -+ */ -+void set_peername_from_env(address_t *p, const char *env) -+{ -+ struct addrinfo hints = { -+ .ai_family = AF_UNSPEC, -+ .ai_socktype = SOCK_STREAM, -+ .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV, -+ }; -+ struct addrinfo *result; -+ char *c; -+ int s; -+ -+ char *val = getenv(env); -+ csync_debug(3, "getenv(%s): >>%s<<\n", env, val ?: ""); -+ if (!val) -+ return; -+ val = strdup(val); -+ if (!val) -+ return; -+ -+ c = strchr(val, ' '); -+ if (!c) -+ return; -+ *c = '\0'; -+ -+ s = getaddrinfo(val, NULL, &hints, &result); -+ if (s != 0) { -+ csync_debug(1, "getaddrinfo: %s\n", gai_strerror(s)); -+ return; -+ } -+ -+ /* getaddrinfo() may return a list of address structures. -+ * Use the first one. */ -+ if (result) -+ memcpy(p, result->ai_addr, result->ai_addrlen); -+ freeaddrinfo(result); -+} -+ - void csync_daemon_session() - { -- struct sockaddr_in peername; -- struct hostent *hp; -- int peerlen = sizeof(struct sockaddr_in); -+ struct stat sb; -+ address_t peername = { .sa.sa_family = AF_UNSPEC, }; -+ socklen_t peerlen = sizeof(peername); - char line[4096], *peer=0, *tag[32]; - int i; - -- if ( getpeername(0, (struct sockaddr*)&peername, &peerlen) == -1 ) -- csync_fatal("Can't run getpeername on fd 0: %s", strerror(errno)); -+ -+ if (fstat(0, &sb)) -+ csync_fatal("Can't run fstat on fd 0: %s", strerror(errno)); -+ -+ switch (sb.st_mode & S_IFMT) { -+ case S_IFSOCK: -+ if ( getpeername(0, &peername.sa, &peerlen) == -1 ) -+ csync_fatal("Can't run getpeername on fd 0: %s", strerror(errno)); -+ break; -+ case S_IFIFO: -+ set_peername_from_env(&peername, "SSH_CLIENT"); -+ break; -+ /* fall through */ -+ default: -+ csync_fatal("I'm only talking to sockets or pipes! %x\n", sb.st_mode & S_IFMT); -+ break; -+ } - - while ( conn_gets(line, 4096) ) { - int cmdnr; -@@ -246,13 +479,8 @@ void csync_daemon_session() - cmd_error = 0; - - if ( cmdtab[cmdnr].need_ident && !peer ) { -- union { -- in_addr_t addr; -- unsigned char oct[4]; -- } tmp; -- tmp.addr = peername.sin_addr.s_addr; -- conn_printf("Dear %d.%d.%d.%d, please identify first.\n", -- tmp.oct[0], tmp.oct[1], tmp.oct[2], tmp.oct[3]); -+ conn_printf("Dear %s, please identify first.\n", -+ csync_inet_ntop(&peername) ?: "stranger"); - goto next_cmd; - } - -@@ -443,8 +671,8 @@ void csync_daemon_session() - strcmp(tag[2], "-") ? url_encode(tag[2]) : "", - strcmp(tag[2], "-") ? "'" : "") - { -- if ( csync_match_file_host(url_decode(SQL_V[1]), tag[1], peer, (const char **)&tag[3]) ) -- conn_printf("%s\t%s\n", SQL_V[0], SQL_V[1]); -+ if ( csync_match_file_host(url_decode(SQL_V(1)), tag[1], peer, (const char **)&tag[3]) ) -+ conn_printf("%s\t%s\n", SQL_V(0), SQL_V(1)); - } SQL_END; - break; - -@@ -454,18 +682,18 @@ void csync_daemon_session() - csync_debug_level = atoi(tag[1]); - break; - case A_HELLO: -- if (peer) free(peer); -- hp = gethostbyname(tag[1]); -- if ( hp != 0 && peername.sin_family == hp->h_addrtype && -- !memcmp(hp->h_addr, &peername.sin_addr, hp->h_length) && -- conn_check_peer_cert(tag[1], 0)) { -+ if (peer) { -+ free(peer); -+ peer = NULL; -+ } -+ if (verify_peername(tag[1], &peername)) { - peer = strdup(tag[1]); - } else { -- peer = 0; -+ peer = NULL; - cmd_error = "Identification failed!"; - break; - } --#ifdef HAVE_LIBGNUTLS_OPENSSL -+#ifdef HAVE_LIBGNUTLS - if (!csync_conn_usessl) { - struct csync_nossl *t; - for (t = csync_nossl; t; t=t->next) { -@@ -507,7 +735,7 @@ found_asactive: ; - break; - case A_BYE: - for (i=0; i<32; i++) -- tag[i] = strdup(url_decode(tag[i])); -+ free(tag[i]); - conn_printf("OK (cu_later).\n"); - return; - } -@@ -529,7 +757,6 @@ abort_cmd: - - next_cmd: - for (i=0; i<32; i++) -- tag[i] = strdup(url_decode(tag[i])); -+ free(tag[i]); - } - } -- -diff --git a/db.c b/db.c -index 1cd6953..68848b3 100644 ---- a/db.c -+++ b/db.c -@@ -19,13 +19,13 @@ - */ - - #include "csync2.h" --#include <sqlite.h> - #include <stdio.h> - #include <stdarg.h> - #include <stdlib.h> - #include <unistd.h> - #include <signal.h> - #include <time.h> -+#include "db_api.h" - - #define DEADLOCK_MESSAGE \ - "Database backend is exceedingly busy => Terminating (requesting retry).\n" -@@ -33,14 +33,16 @@ - int db_blocking_mode = 1; - int db_sync_mode = 1; - --static sqlite *db = 0; -+extern int db_type; -+static db_conn_p db = 0; -+// TODO make configurable -+int wait = 1; - - static int get_dblock_timeout() - { -- return getpid() % 7 + 12; -+ return getpid() % 7 + csync_lock_timeout; - } - -- - static int tqueries_counter = -50; - static time_t transaction_begin = 0; - static time_t last_wait_cycle = 0; -@@ -58,7 +60,7 @@ void csync_db_alarmhandler(int signum) - begin_commit_recursion++; - - csync_debug(2, "Database idle in transaction. Forcing COMMIT.\n"); -- SQL("COMMIT TRANSACTION", "COMMIT TRANSACTION"); -+ SQL("COMMIT ", "COMMIT "); - tqueries_counter = -10; - - begin_commit_recursion--; -@@ -82,7 +84,7 @@ void csync_db_maybegin() - transaction_begin = time(0); - if (!last_wait_cycle) - last_wait_cycle = transaction_begin; -- SQL("BEGIN TRANSACTION", "BEGIN TRANSACTION"); -+ SQL("BEGIN ", "BEGIN "); - } - - begin_commit_recursion--; -@@ -103,9 +105,11 @@ void csync_db_maycommit() - now = time(0); - - if ((now - last_wait_cycle) > 10) { -- SQL("COMMIT TRANSACTION", "COMMIT TRANSACTION"); -- csync_debug(2, "Waiting 2 secs so others can lock the database (%d - %d)...\n", (int)now, (int)last_wait_cycle); -- sleep(2); -+ SQL("COMMIT", "COMMIT "); -+ if (wait) { -+ csync_debug(2, "Waiting %d secs so others can lock the database (%d - %d)...\n", wait, (int)now, (int)last_wait_cycle); -+ sleep(wait); -+ } - last_wait_cycle = 0; - tqueries_counter = -10; - begin_commit_recursion--; -@@ -113,7 +117,7 @@ void csync_db_maycommit() - } - - if ((tqueries_counter > 1000) || ((now - transaction_begin) > 3)) { -- SQL("COMMIT TRANSACTION", "COMMIT TRANSACTION"); -+ SQL("COMMIT ", "COMMIT "); - tqueries_counter = 0; - begin_commit_recursion--; - return; -@@ -128,45 +132,23 @@ void csync_db_maycommit() - - void csync_db_open(const char *file) - { -- db = sqlite_open(file, 0, 0); -- if ( db == 0 ) -+ int rc = db_open(file, db_type, &db); -+ if ( rc != DB_OK ) - csync_fatal("Can't open database: %s\n", file); - -+ db_set_logger(db, csync_debug); -+ - /* ignore errors on table creation */ - in_sql_query++; -- sqlite_exec(db, -- "CREATE TABLE file (" -- " filename, checktxt," -- " UNIQUE ( filename ) ON CONFLICT REPLACE" -- ")", -- 0, 0, 0); -- sqlite_exec(db, -- "CREATE TABLE dirty (" -- " filename, force, myname, peername," -- " UNIQUE ( filename, peername ) ON CONFLICT IGNORE" -- ")", -- 0, 0, 0); -- sqlite_exec(db, -- "CREATE TABLE hint (" -- " filename, recursive," -- " UNIQUE ( filename, recursive ) ON CONFLICT IGNORE" -- ")", -- 0, 0, 0); -- sqlite_exec(db, -- "CREATE TABLE action (" -- " filename, command, logfile," -- " UNIQUE ( filename, command ) ON CONFLICT IGNORE" -- ")", -- 0, 0, 0); -- sqlite_exec(db, -- "CREATE TABLE x509_cert (" -- " peername, certdata," -- " UNIQUE ( peername ) ON CONFLICT IGNORE" -- ")", -- 0, 0, 0); -+ -+ if (db_schema_version(db) < DB_SCHEMA_VERSION) -+ if (db_upgrade_to_schema(db, DB_SCHEMA_VERSION) != DB_OK) -+ csync_fatal("Cannot create database tables (version requested = %d): %s\n", DB_SCHEMA_VERSION, db_errmsg(db)); -+ - if (!db_sync_mode) -- sqlite_exec(db, "PRAGMA synchronous = OFF", 0, 0, 0); -+ db_exec(db, "PRAGMA synchronous = OFF"); - in_sql_query--; -+ // return db; - } - - void csync_db_close() -@@ -175,10 +157,10 @@ void csync_db_close() - - begin_commit_recursion++; - if (tqueries_counter > 0) { -- SQL("COMMIT TRANSACTION", "COMMIT TRANSACTION"); -+ SQL("COMMIT ", "COMMIT "); - tqueries_counter = -10; - } -- sqlite_close(db); -+ db_close(db); - begin_commit_recursion--; - db = 0; - } -@@ -190,7 +172,7 @@ void csync_db_sql(const char *err, const char *fmt, ...) - int rc, busyc = 0; - - va_start(ap, fmt); -- vasprintf(&sql, fmt, ap); -+ VASPRINTF(&sql, fmt, ap); - va_end(ap); - - in_sql_query++; -@@ -199,15 +181,15 @@ void csync_db_sql(const char *err, const char *fmt, ...) - csync_debug(2, "SQL: %s\n", sql); - - while (1) { -- rc = sqlite_exec(db, sql, 0, 0, 0); -- if ( rc != SQLITE_BUSY ) break; -- if (busyc++ > get_dblock_timeout()) { db = 0; csync_fatal(DEADLOCK_MESSAGE); } -- csync_debug(2, "Database is busy, sleeping a sec.\n"); -- sleep(1); -+ rc = db_exec(db, sql); -+ if ( rc != DB_BUSY ) break; -+ if (busyc++ > get_dblock_timeout()) { db = 0; csync_fatal(DEADLOCK_MESSAGE); } -+ csync_debug(2, "Database is busy, sleeping a sec.\n"); -+ sleep(1); - } - -- if ( rc != SQLITE_OK && err ) -- csync_fatal("Database Error: %s [%d]: %s\n", err, rc, sql); -+ if ( rc != DB_OK && err ) -+ csync_fatal("Database Error: %s [%d]: %s on executing %s\n", err, rc, db_errmsg(db), sql); - free(sql); - - csync_db_maycommit(); -@@ -216,77 +198,140 @@ void csync_db_sql(const char *err, const char *fmt, ...) - - void* csync_db_begin(const char *err, const char *fmt, ...) - { -- sqlite_vm *vm; -+ db_stmt_p stmt = NULL; - char *sql; - va_list ap; - int rc, busyc = 0; -- -+ char *ppTail; - va_start(ap, fmt); -- vasprintf(&sql, fmt, ap); -+ VASPRINTF(&sql, fmt, ap); - va_end(ap); - - in_sql_query++; - csync_db_maybegin(); - - csync_debug(2, "SQL: %s\n", sql); -- - while (1) { -- rc = sqlite_compile(db, sql, 0, &vm, 0); -- if ( rc != SQLITE_BUSY ) break; -+ rc = db_prepare_stmt(db, sql, &stmt, &ppTail); -+ if ( rc != DB_BUSY ) break; - if (busyc++ > get_dblock_timeout()) { db = 0; csync_fatal(DEADLOCK_MESSAGE); } - csync_debug(2, "Database is busy, sleeping a sec.\n"); - sleep(1); - } - -- if ( rc != SQLITE_OK && err ) -- csync_fatal("Database Error: %s [%d]: %s\n", err, rc, sql); -+ if ( rc != DB_OK && err ) -+ csync_fatal("Database Error: %s [%d]: %s on executing %s\n", err, rc, db_errmsg(db), sql); - free(sql); - -- return vm; -+ return stmt; -+} -+ -+const char *csync_db_get_column_text(void *stmt, int column) { -+ return db_stmt_get_column_text(stmt, column); -+} -+ -+int csync_db_get_column_int(void *stmt, int column) { -+ return db_stmt_get_column_int((db_stmt_p) stmt, column); - } - - int csync_db_next(void *vmx, const char *err, - int *pN, const char ***pazValue, const char ***pazColName) - { -- sqlite_vm *vm = vmx; -+ db_stmt_p stmt = vmx; - int rc, busyc = 0; - - csync_debug(4, "Trying to fetch a row from the database.\n"); - - while (1) { -- rc = sqlite_step(vm, pN, pazValue, pazColName); -- if ( rc != SQLITE_BUSY ) break; -- if (busyc++ > get_dblock_timeout()) { db = 0; csync_fatal(DEADLOCK_MESSAGE); } -+ rc = db_stmt_next(stmt); -+ if ( rc != DB_BUSY ) -+ break; -+ if (busyc++ > get_dblock_timeout()) { -+ db = 0; -+ csync_fatal(DEADLOCK_MESSAGE); -+ } - csync_debug(2, "Database is busy, sleeping a sec.\n"); - sleep(1); - } - -- if ( rc != SQLITE_OK && rc != SQLITE_ROW && -- rc != SQLITE_DONE && err ) -- csync_fatal("Database Error: %s [%d].\n", err, rc); -+ if ( rc != DB_OK && rc != DB_ROW && -+ rc != DB_DONE && err ) -+ csync_fatal("Database Error: %s [%d]: %s\n", err, rc, db_errmsg(db)); -+ -+ return rc == DB_ROW; -+} - -- return rc == SQLITE_ROW; -+const void * csync_db_colblob(void *stmtx, int col) { -+ db_stmt_p stmt = stmtx; -+ const void *ptr = stmt->get_column_blob(stmt, col); -+ if (stmt->db && stmt->db->logger) { -+ stmt->db->logger(4, "DB get blob: %s ", (char *) ptr); -+ } -+ return ptr; - } - - void csync_db_fin(void *vmx, const char *err) - { -- sqlite_vm *vm = vmx; -+ db_stmt_p stmt = (db_stmt_p) vmx; - int rc, busyc = 0; - -+ if (vmx == NULL) -+ return; -+ - csync_debug(2, "SQL Query finished.\n"); - - while (1) { -- rc = sqlite_finalize(vm, 0); -- if ( rc != SQLITE_BUSY ) break; -- if (busyc++ > get_dblock_timeout()) { db = 0; csync_fatal(DEADLOCK_MESSAGE); } -- csync_debug(2, "Database is busy, sleeping a sec.\n"); -- sleep(1); -+ rc = db_stmt_close(stmt); -+ if ( rc != DB_BUSY ) -+ break; -+ if (busyc++ > get_dblock_timeout()) { db = 0; csync_fatal(DEADLOCK_MESSAGE); } -+ csync_debug(2, "Database is busy, sleeping a sec.\n"); -+ sleep(1); - } - -- if ( rc != SQLITE_OK && err ) -- csync_fatal("Database Error: %s [%d].\n", err, rc); -+ if ( rc != DB_OK && err ) -+ csync_fatal("Database Error: %s [%d]: %s\n", err, rc, db_errmsg(db)); - - csync_db_maycommit(); - in_sql_query--; - } - -+#if defined(HAVE_SQLITE) -+#define DBEXTENSION ".db" -+#endif -+#if defined(HAVE_SQLITE3) -+#define DBEXTENSION ".db3" -+#endif -+ -+char *db_default_database(char *dbdir, char *myhostname, char *cfg_name) -+{ -+ char *db; -+ -+#if defined(HAVE_SQLITE3) -+ if (cfg_name[0] != '\0') -+ ASPRINTF(&db, "sqlite3://%s/%s_%s" DBEXTENSION, dbdir, myhostname, cfgname); -+ else -+ ASPRINTF(&db, "sqlite3://%s/%s" DBEXTENSION, dbdir, myhostname); -+#elif defined(HAVE_SQLITE) -+ if (cfg_name[0] != '\0') -+ ASPRINTF(&db, "sqlite2://%s/%s_%s" DBEXTENSION, dbdir, myhostname, cfgname); -+ else -+ ASPRINTF(&db, "sqlite2://%s/%s" DBEXTENSION, dbdir, myhostname); -+#elif defined(HAVE_MYSQL) -+ if (cfg_name[0] != '\0') -+ ASPRINTF(&db, "mysql://root@localhost/csync2_%s_%s" DBEXTENSION, myhostname, cfgname); -+ else -+ ASPRINTF(&db, "mysql://root@localhost/csync2_%s" DBEXTENSION, myhostname); -+ -+#elif defined(HAVE_POSTGRES) -+ if (cfg_name[0] != '\0') -+ ASPRINTF(&db, "pgsql://root@localhost/csync2_%s_%s" DBEXTENSION, myhostname, cfgname); -+ else -+ ASPRINTF(&db, "pgsql://root@localhost/csync2_%s" DBEXTENSION, myhostname); -+ -+#else -+#error "No database backend available. Please install either libpg, libmysqlclient or libsqlite, reconfigure and recompile" -+#endif -+ -+ return db; -+} -diff --git a/db_api.c b/db_api.c -new file mode 100644 -index 0000000..af5591c ---- /dev/null -+++ b/db_api.c -@@ -0,0 +1,186 @@ -+/* -+ DB API -+ -+ */ -+ -+#include "csync2.h" -+#include <stdio.h> -+#include <stdarg.h> -+#include <stdlib.h> -+#include <unistd.h> -+#include <signal.h> -+#include <time.h> -+#include "db_api.h" -+ -+#include "db_mysql.h" -+#include "db_postgres.h" -+#include "db_sqlite.h" -+#include "db_sqlite2.h" -+ -+#define DEADLOCK_MESSAGE \ -+ "Database backend is exceedingly busy => Terminating (requesting retry).\n" -+ -+int db_sqlite_open(const char *file, db_conn_p *db); -+int db_mysql_open(const char *file, db_conn_p *db); -+ -+int db_detect_type(const char **db_str, int type) { -+ const char *db_types[] = { "mysql://", "sqlite3://", "sqlite2://", "pgsql://", 0 }; -+ int types[] = { DB_MYSQL, DB_SQLITE3, DB_SQLITE2, DB_PGSQL }; -+ int index; -+ for (index = 0; 1 ; index++) { -+ if (db_types[index] == 0) -+ break; -+ if (!strncmp(*db_str, db_types[index], strlen(db_types[index]))) { -+ *db_str += strlen(db_types[index]); -+ return types[index]; -+ } -+ } -+ return type; -+} -+ -+int db_open(const char *file, int type, db_conn_p *db) -+{ -+ int rc = DB_ERROR; -+ const char *db_str; -+ db_str = file; -+ -+ type = db_detect_type(&db_str, type); -+ /* Switch between implementation */ -+ switch (type) { -+ case DB_SQLITE2: -+ rc = db_sqlite2_open(db_str, db); -+ -+ if (rc != DB_OK && db_str[0] != '/') -+ fprintf(csync_debug_out, "Cannot open database file: %s, maybe you need three slashes (like sqlite:///var/lib/csync2/csync2.db)\n", db_str); -+ break; -+ case DB_SQLITE3: -+ rc = db_sqlite_open(db_str, db); -+ -+ if (rc != DB_OK && db_str[0] != '/') -+ fprintf(csync_debug_out, "Cannot open database file: %s, maybe you need three slashes (like sqlite:///var/lib/csync2/csync2.db)\n", db_str); -+ break; -+#ifdef HAVE_MYSQL -+ case DB_MYSQL: -+ rc = db_mysql_open(db_str, db); -+ break; -+#else -+ case DB_MYSQL: -+ csync_fatal("No Mysql support configured. Please reconfigure with --enable-mysql (database is %s).\n", file); -+ rc = DB_ERROR; -+ break; -+#endif -+#ifdef HAVE_POSTGRES -+ case DB_PGSQL: -+ rc = db_postgres_open(db_str, db); -+ break; -+#else -+ case DB_PGSQL: -+ csync_fatal("No Postgres SQL support configured. Please reconfigure with --enable-postgres (database is %s).\n", file); -+ rc = DB_ERROR; -+ break; -+#endif -+ -+ default: -+ csync_fatal("Database type not found. Can't open database %s\n", file); -+ rc = DB_ERROR; -+ } -+ if (*db) -+ (*db)->logger = 0; -+ return rc; -+} -+ -+void db_set_logger(db_conn_p conn, void (*logger)(int lv, const char *fmt, ...)) { -+ if (conn == NULL) -+ csync_fatal("No connection in set_logger.\n"); -+ -+ conn->logger = logger; -+} -+ -+void db_close(db_conn_p conn) -+{ -+ if (!conn || !conn->close) -+ return; -+ conn->close(conn); -+} -+ -+const char *db_errmsg(db_conn_p conn) -+{ -+ if (conn && conn->errmsg) -+ return conn->errmsg(conn); -+ -+ return "(no error message function available)"; -+} -+ -+int db_exec(db_conn_p conn, const char *sql) { -+ if (conn && conn->exec) -+ return conn->exec(conn, sql); -+ -+ csync_debug(0, "No exec function in db_exec.\n"); -+ return DB_ERROR; -+} -+ -+int db_prepare_stmt(db_conn_p conn, const char *sql, db_stmt_p *stmt, char **pptail) { -+ if (conn && conn->prepare) -+ return conn->prepare(conn, sql, stmt, pptail); -+ -+ csync_debug(0, "No prepare function in db_prepare_stmt.\n"); -+ return DB_ERROR; -+} -+ -+const char *db_stmt_get_column_text(db_stmt_p stmt, int column) { -+ if (stmt && stmt->get_column_text) -+ return stmt->get_column_text(stmt, column); -+ -+ csync_debug(0, "No stmt in db_stmt_get_column_text / no function.\n"); -+ return NULL; -+} -+ -+int db_stmt_get_column_int(db_stmt_p stmt, int column) { -+ if (stmt && stmt->get_column_int) -+ return stmt->get_column_int(stmt, column); -+ -+ csync_debug(0, "No stmt in db_stmt_get_column_int / no function.\n"); -+ return 0; -+} -+ -+int db_stmt_next(db_stmt_p stmt) -+{ -+ if (stmt && stmt->next) -+ return stmt->next(stmt); -+ -+ csync_debug(0, "No stmt in db_stmt_next / no function.\n"); -+ return DB_ERROR; -+} -+ -+int db_stmt_close(db_stmt_p stmt) -+{ -+ if (stmt && stmt->close) -+ return stmt->close(stmt); -+ -+ csync_debug(0, "No stmt in db_stmt_close / no function.\n"); -+ return DB_ERROR; -+} -+ -+int db_schema_version(db_conn_p db) -+{ -+ int version = -1; -+ -+ SQL_BEGIN(NULL, /* ignore errors */ -+ "SELECT count(*) from file") -+ { -+ version = 0; -+ } SQL_END; -+ -+ return version; -+} -+ -+ -+int db_upgrade_to_schema(db_conn_p db, int version) -+{ -+ if (db && db->upgrade_to_schema) -+ return db->upgrade_to_schema(version); -+ -+ return DB_ERROR; -+} -+ -+ -diff --git a/db_api.h b/db_api.h -new file mode 100644 -index 0000000..eab627b ---- /dev/null -+++ b/db_api.h -@@ -0,0 +1,62 @@ -+ -+#ifndef DB_API_H -+#define DB_API_H -+ -+#define DB_SQLITE2 1 -+#define DB_SQLITE3 2 -+#define DB_MYSQL 3 -+#define DB_PGSQL 4 -+ -+#define DB_OK 0 -+#define DB_ERROR 1 -+#define DB_BUSY 2 -+#define DB_NO_CONNECTION 3 -+#define DB_NO_CONNECTION_REAL 4 -+#define DB_ROW 100 -+#define DB_DONE 101 -+ -+typedef struct db_conn_t *db_conn_p; -+typedef struct db_stmt_t *db_stmt_p; -+ -+struct db_conn_t { -+ void *private; -+ int (*exec) (db_conn_p conn, const char* exec); -+ int (*prepare)(db_conn_p conn, const char *statement, db_stmt_p *stmt, char **value); -+ void (*close) (db_conn_p conn); -+ void (*logger) (int lv, const char *fmt, ...); -+ const char* (*errmsg) (db_conn_p conn); -+ int (*upgrade_to_schema) (int version); -+}; -+ -+struct db_stmt_t { -+ void *private; -+ void *private2; -+ db_conn_p db; -+ const char * (*get_column_text) (db_stmt_p vmx, int column); -+ const void* (*get_column_blob) (db_stmt_p vmx, int column); -+ int (*get_column_int) (db_stmt_p vmx, int column); -+ int (*next) (db_stmt_p stmt); -+ int (*close)(db_stmt_p stmt); -+}; -+ -+//struct db_conn *db_conn; -+ -+int db_open(const char *file, int type, db_conn_p *db); -+void db_close(db_conn_p conn); -+ -+int db_exec(db_conn_p conn, const char* exec); -+int db_exec2(db_conn_p conn, const char* exec, void (*callback)(void *, int, int), void *data, const char **err); -+ -+int db_prepare_stmt(db_conn_p conn, const char *statement, db_stmt_p *stmt, char **value); -+ -+const char * db_stmt_get_column_text(db_stmt_p stmt, int column); -+int db_stmt_get_column_int(db_stmt_p stmt, int column); -+int db_stmt_next (db_stmt_p stmt); -+int db_stmt_close(db_stmt_p stmt); -+ -+void db_set_logger(db_conn_p conn, void (*logger)(int lv, const char *fmt, ...)); -+int db_schema_version(db_conn_p db); -+int db_upgrade_to_schema(db_conn_p db, int version); -+const char *db_errmsg(db_conn_p conn); -+ -+#endif -diff --git a/db_mysql.c b/db_mysql.c -new file mode 100644 -index 0000000..1b6d09e ---- /dev/null -+++ b/db_mysql.c -@@ -0,0 +1,408 @@ -+/* -+ * Copyright (C) 2010 Dennis Schafroth <dennis@schafroth.com>> -+ * Copyright (C) 2010 Johannes Thoma <johannes.thoma@gmx.at> -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation; either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software -+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+ */ -+ -+#include "csync2.h" -+#include <stdio.h> -+#include <stdarg.h> -+#include <stdlib.h> -+#include <unistd.h> -+#include <signal.h> -+#include <time.h> -+#include <string.h> -+#include "db_api.h" -+#include "db_mysql.h" -+#include "dl.h" -+ -+#ifdef HAVE_MYSQL -+#include <mysql/mysql.h> -+#include <mysql/mysqld_error.h> -+ -+static struct db_mysql_fns { -+ MYSQL *(*mysql_init_fn)(MYSQL*); -+ MYSQL *(*mysql_real_connect_fn)(MYSQL *, const char *, const char *, const char *, const char *, unsigned int, const char *, unsigned long); -+ int (*mysql_errno_fn)(MYSQL*); -+ int (*mysql_query_fn)(MYSQL*, const char*); -+ void (*mysql_close_fn)(MYSQL*); -+ const char *(*mysql_error_fn)(MYSQL *); -+ MYSQL_RES *(*mysql_store_result_fn)(MYSQL *); -+ unsigned int (*mysql_num_fields_fn)(MYSQL_RES *); -+ MYSQL_ROW (*mysql_fetch_row_fn)(MYSQL_RES *); -+ void (*mysql_free_result_fn)(MYSQL_RES *); -+ unsigned int (*mysql_warning_count_fn)(MYSQL *); -+} f; -+ -+static void *dl_handle; -+ -+ -+static void db_mysql_dlopen(void) -+{ -+ csync_debug(1, "Opening shared library libmysqlclient.so\n"); -+ dl_handle = dlopen("libmysqlclient.so", RTLD_LAZY); -+ if (dl_handle == NULL) { -+ csync_fatal("Could not open libmysqlclient.so: %s\nPlease install Mysql client library (libmysqlclient) or use other database (sqlite, postgres)\n", dlerror()); -+ } -+ -+ csync_debug(1, "Reading symbols from shared library libmysqlclient.so\n"); -+ -+ LOOKUP_SYMBOL(dl_handle, mysql_init); -+ LOOKUP_SYMBOL(dl_handle, mysql_real_connect); -+ LOOKUP_SYMBOL(dl_handle, mysql_errno); -+ LOOKUP_SYMBOL(dl_handle, mysql_query); -+ LOOKUP_SYMBOL(dl_handle, mysql_close); -+ LOOKUP_SYMBOL(dl_handle, mysql_error); -+ LOOKUP_SYMBOL(dl_handle, mysql_store_result); -+ LOOKUP_SYMBOL(dl_handle, mysql_num_fields); -+ LOOKUP_SYMBOL(dl_handle, mysql_fetch_row); -+ LOOKUP_SYMBOL(dl_handle, mysql_free_result); -+ LOOKUP_SYMBOL(dl_handle, mysql_warning_count); -+} -+ -+ -+int db_mysql_parse_url(char *url, char **host, char **user, char **pass, char **database, unsigned int *port, char **unix_socket) -+{ -+ char *pos = strchr(url, '@'); -+ if (pos) { -+ // Optional user/passwd -+ *(pos) = 0; -+ *(user) = url; -+ url = pos + 1; -+ // TODO password -+ pos = strchr(*user, ':'); -+ if (pos) { -+ *(pos) = 0; -+ *(pass) = (pos +1); -+ } -+ else -+ *pass = 0; -+ } -+ else { -+ // No user/pass password -+ *user = 0; -+ *pass = 0; -+ } -+ *host = url; -+ pos = strchr(*host, '/'); -+ if (pos) { -+ // Database -+ (*pos) = 0; -+ *database = pos+1; -+ } -+ else { -+ *database = 0; -+ } -+ pos = strchr(*host, ':'); -+ if (pos) { -+ (*pos) = 0; -+ *port = atoi(pos+1); -+ } -+ *unix_socket = 0; -+ return DB_OK; -+} -+ -+#endif -+ -+int db_mysql_open(const char *file, db_conn_p *conn_p) -+{ -+#ifdef HAVE_MYSQL -+ db_mysql_dlopen(); -+ -+ MYSQL *db = f.mysql_init_fn(0); -+ char *host, *user, *pass, *database, *unix_socket; -+ unsigned int port; -+ char *db_url = malloc(strlen(file)+1); -+ char *create_database_statement; -+ -+ if (db_url == NULL) -+ csync_fatal("No memory for db_url\n"); -+ -+ strcpy(db_url, file); -+ int rc = db_mysql_parse_url(db_url, &host, &user, &pass, &database, &port, &unix_socket); -+ if (rc != DB_OK) { -+ return rc; -+ } -+ -+ if (f.mysql_real_connect_fn(db, host, user, pass, database, port, unix_socket, 0) == NULL) { -+ if (f.mysql_errno_fn(db) == ER_BAD_DB_ERROR) { -+ if (f.mysql_real_connect_fn(db, host, user, pass, NULL, port, unix_socket, 0) != NULL) { -+ ASPRINTF(&create_database_statement, "create database %s", database); -+ -+ csync_debug(2, "creating database %s\n", database); -+ if (f.mysql_query_fn(db, create_database_statement) != 0) -+ csync_fatal("Cannot create database %s: Error: %s\n", database, f.mysql_error_fn(db)); -+ free(create_database_statement); -+ -+ f.mysql_close_fn(db); -+ db = f.mysql_init_fn(0); -+ -+ if (f.mysql_real_connect_fn(db, host, user, pass, database, port, unix_socket, 0) == NULL) -+ goto fatal; -+ } -+ } else -+fatal: -+ csync_fatal("Failed to connect to database: Error: %s\n", f.mysql_error_fn(db)); -+ } -+ -+ db_conn_p conn = calloc(1, sizeof(*conn)); -+ if (conn == NULL) { -+ return DB_ERROR; -+ } -+ *conn_p = conn; -+ conn->private = db; -+ conn->close = db_mysql_close; -+ conn->exec = db_mysql_exec; -+ conn->prepare = db_mysql_prepare; -+ conn->errmsg = db_mysql_errmsg; -+ conn->upgrade_to_schema = db_mysql_upgrade_to_schema; -+ -+ return rc; -+#else -+ return DB_ERROR; -+#endif -+} -+ -+#ifdef HAVE_MYSQL -+ -+void db_mysql_close(db_conn_p conn) -+{ -+ if (!conn) -+ return; -+ if (!conn->private) -+ return; -+ f.mysql_close_fn(conn->private); -+ conn->private = 0; -+} -+ -+const char *db_mysql_errmsg(db_conn_p conn) -+{ -+ if (!conn) -+ return "(no connection)"; -+ if (!conn->private) -+ return "(no private data in conn)"; -+ return f.mysql_error_fn(conn->private); -+} -+ -+static void print_warnings(int level, MYSQL *m) -+{ -+ int rc; -+ MYSQL_RES *res; -+ int fields; -+ MYSQL_ROW row; -+ -+ if (m == NULL) -+ csync_fatal("print_warnings: m is NULL"); -+ -+ rc = f.mysql_query_fn(m, "SHOW WARNINGS"); -+ if (rc != 0) -+ csync_fatal("print_warnings: Failed to get warning messages"); -+ -+ res = f.mysql_store_result_fn(m); -+ if (res == NULL) -+ csync_fatal("print_warnings: Failed to get result set for warning messages"); -+ -+ fields = f.mysql_num_fields_fn(res); -+ if (fields < 2) -+ csync_fatal("print_warnings: Strange: show warnings result set has less than 2 rows"); -+ -+ row = f.mysql_fetch_row_fn(res); -+ -+ while (row) { -+ csync_debug(level, "MySql Warning: %s\n", row[2]); -+ row = f.mysql_fetch_row_fn(res); -+ } -+ -+ f.mysql_free_result_fn(res); -+} -+ -+int db_mysql_exec(db_conn_p conn, const char *sql) -+{ -+ int rc = DB_ERROR; -+ if (!conn) -+ return DB_NO_CONNECTION; -+ -+ if (!conn->private) { -+ /* added error element */ -+ return DB_NO_CONNECTION_REAL; -+ } -+ rc = f.mysql_query_fn(conn->private, sql); -+ -+/* Treat warnings as errors. For example when a column is too short this should -+ be an error. */ -+ -+ if (f.mysql_warning_count_fn(conn->private) > 0) { -+ print_warnings(1, conn->private); -+ return DB_ERROR; -+ } -+ -+ /* On error parse, create DB ERROR element */ -+ return rc; -+} -+ -+int db_mysql_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, -+ char **pptail) { -+ int rc = DB_ERROR; -+ -+ *stmt_p = NULL; -+ -+ if (!conn) -+ return DB_NO_CONNECTION; -+ -+ if (!conn->private) { -+ /* added error element */ -+ return DB_NO_CONNECTION_REAL; -+ } -+ db_stmt_p stmt = malloc(sizeof(*stmt)); -+ /* TODO avoid strlen, use configurable limit? */ -+ rc = f.mysql_query_fn(conn->private, sql); -+ -+/* Treat warnings as errors. For example when a column is too short this should -+ be an error. */ -+ -+ if (f.mysql_warning_count_fn(conn->private) > 0) { -+ print_warnings(1, conn->private); -+ return DB_ERROR; -+ } -+ -+ MYSQL_RES *mysql_stmt = f.mysql_store_result_fn(conn->private); -+ if (mysql_stmt == NULL) { -+ csync_debug(2, "Error in mysql_store_result: %s", f.mysql_error_fn(conn->private)); -+ return DB_ERROR; -+ } -+ -+/* Treat warnings as errors. For example when a column is too short this should -+ be an error. */ -+ -+ if (f.mysql_warning_count_fn(conn->private) > 0) { -+ print_warnings(1, conn->private); -+ return DB_ERROR; -+ } -+ -+ stmt->private = mysql_stmt; -+ /* TODO error mapping / handling */ -+ *stmt_p = stmt; -+ stmt->get_column_text = db_mysql_stmt_get_column_text; -+ stmt->get_column_blob = db_mysql_stmt_get_column_blob; -+ stmt->get_column_int = db_mysql_stmt_get_column_int; -+ stmt->next = db_mysql_stmt_next; -+ stmt->close = db_mysql_stmt_close; -+ stmt->db = conn; -+ return DB_OK; -+} -+ -+const void* db_mysql_stmt_get_column_blob(db_stmt_p stmt, int column) { -+ if (!stmt || !stmt->private2) { -+ return 0; -+ } -+ MYSQL_ROW row = stmt->private2; -+ return row[column]; -+} -+ -+const char *db_mysql_stmt_get_column_text(db_stmt_p stmt, int column) { -+ if (!stmt || !stmt->private2) { -+ return 0; -+ } -+ MYSQL_ROW row = stmt->private2; -+ return row[column]; -+} -+ -+int db_mysql_stmt_get_column_int(db_stmt_p stmt, int column) { -+ const char *value = db_mysql_stmt_get_column_text(stmt, column); -+ if (value) -+ return atoi(value); -+ /* error mapping */ -+ return 0; -+} -+ -+ -+int db_mysql_stmt_next(db_stmt_p stmt) -+{ -+ MYSQL_RES *mysql_stmt = stmt->private; -+ stmt->private2 = f.mysql_fetch_row_fn(mysql_stmt); -+ /* error mapping */ -+ if (stmt->private2) -+ return DB_ROW; -+ return DB_DONE; -+} -+ -+int db_mysql_stmt_close(db_stmt_p stmt) -+{ -+ MYSQL_RES *mysql_stmt = stmt->private; -+ f.mysql_free_result_fn(mysql_stmt); -+ free(stmt); -+ return DB_OK; -+} -+ -+ -+int db_mysql_upgrade_to_schema(int version) -+{ -+ if (version < 0) -+ return DB_OK; -+ -+ if (version > 0) -+ return DB_ERROR; -+ -+ csync_debug(2, "Upgrading database schema to version %d.\n", version); -+ -+/* We want proper logging, so use the csync sql function instead -+ * of that from the database layer. -+ */ -+ csync_db_sql("Creating action table", -+ "CREATE TABLE `action` (" -+ " `filename` varchar(4096) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL," -+ " `command` text," -+ " `logfile` text," -+ " UNIQUE KEY `filename` (`filename`(326),`command`(20))" -+ ")"); -+ -+ csync_db_sql("Creating dirty table", -+ "CREATE TABLE `dirty` (" -+ " `filename` varchar(4096) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL," -+ " `forced` int(11) DEFAULT NULL," -+ " `myname` varchar(50) DEFAULT NULL," -+ " `peername` varchar(50) DEFAULT NULL," -+ " UNIQUE KEY `filename` (`filename`(316),`peername`)," -+ " KEY `dirty_host` (`peername`(10))" -+ ")"); -+ -+ csync_db_sql("Creating file table", -+ "CREATE TABLE `file` (" -+ " `filename` varchar(4096) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL," -+ " `checktxt` varchar(200) DEFAULT NULL," -+ " UNIQUE KEY `filename` (`filename`(333))" -+ ")"); -+ -+ csync_db_sql("Creating hint table", -+ "CREATE TABLE `hint` (" -+ " `filename` varchar(4096) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL," -+ " `recursive` int(11) DEFAULT NULL" -+ ")"); -+ -+ csync_db_sql("Creating x509_cert table", -+ "CREATE TABLE `x509_cert` (" -+ " `peername` varchar(50) DEFAULT NULL," -+ " `certdata` varchar(255) DEFAULT NULL," -+ " UNIQUE KEY `peername` (`peername`)" -+ ")"); -+ -+/* csync_db_sql does a csync_fatal on error, so we always return DB_OK here. */ -+ -+ return DB_OK; -+} -+ -+ -+#endif -diff --git a/db_mysql.h b/db_mysql.h -new file mode 100644 -index 0000000..c5aeab3 ---- /dev/null -+++ b/db_mysql.h -@@ -0,0 +1,19 @@ -+ -+#ifndef DB_MYSQL_H -+#define DB_MYSQL_H -+ -+/* public */ -+int db_mysql_open(const char *file, db_conn_p *conn_p); -+/* Private */ -+void db_mysql_close(db_conn_p db_conn); -+int db_mysql_exec(db_conn_p conn, const char *sql); -+int db_mysql_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, char **pptail); -+int db_mysql_stmt_next(db_stmt_p stmt); -+const void* db_mysql_stmt_get_column_blob(db_stmt_p stmt, int column); -+const char *db_mysql_stmt_get_column_text(db_stmt_p stmt, int column); -+int db_mysql_stmt_get_column_int(db_stmt_p stmt, int column); -+int db_mysql_stmt_close(db_stmt_p stmt); -+const char *db_mysql_errmsg(db_conn_p db_conn); -+int db_mysql_upgrade_to_schema(int version); -+ -+#endif -diff --git a/db_postgres.c b/db_postgres.c -new file mode 100644 -index 0000000..b40bdfb ---- /dev/null -+++ b/db_postgres.c -@@ -0,0 +1,458 @@ -+/* -+ * Copyright (C) 2010 Dennis Schafroth <dennis@schafroth.com> -+ * Copyright (C) 2010 Johannes Thoma <johannes.thoma@gmx.at> -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation; either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software -+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+ */ -+ -+#include "csync2.h" -+#include <stdio.h> -+#include <stdarg.h> -+#include <stdlib.h> -+#include <unistd.h> -+#include <signal.h> -+#include <time.h> -+#include <string.h> -+#include "db_api.h" -+#include "db_postgres.h" -+#include "dl.h" -+ -+#ifdef HAVE_POSTGRES -+#include <postgresql/libpq-fe.h> -+#endif -+ -+#if (!defined HAVE_POSTGRES) -+int db_postgres_open(const char *file, db_conn_p *conn_p) -+{ -+ return DB_ERROR; -+} -+#else -+ -+static struct db_postgres_fns { -+ PGconn *(*PQconnectdb_fn)(char *); -+ ConnStatusType (*PQstatus_fn)(const PGconn *); -+ char *(*PQerrorMessage_fn)(const PGconn *); -+ void (*PQfinish_fn)(PGconn *); -+ PGresult *(*PQexec_fn)(PGconn *, const char *); -+ ExecStatusType (*PQresultStatus_fn)(const PGresult *); -+ char *(*PQresultErrorMessage_fn)(const PGresult *); -+ void (*PQclear_fn)(PGresult *); -+ int (*PQntuples_fn)(const PGresult *); -+ char *(*PQgetvalue_fn)(const PGresult *, int, int); -+} f; -+ -+static void *dl_handle; -+ -+ -+static void db_postgres_dlopen(void) -+{ -+ csync_debug(1, "Opening shared library libpq.so\n"); -+ -+ dl_handle = dlopen("libpq.so", RTLD_LAZY); -+ if (dl_handle == NULL) { -+ csync_fatal("Could not open libpq.so: %s\nPlease install postgres client library (libpg) or use other database (sqlite, mysql)\n", dlerror()); -+ } -+ csync_debug(1, "Reading symbols from shared library libpq.so\n"); -+ -+ LOOKUP_SYMBOL(dl_handle, PQconnectdb); -+ LOOKUP_SYMBOL(dl_handle, PQstatus); -+ LOOKUP_SYMBOL(dl_handle, PQerrorMessage); -+ LOOKUP_SYMBOL(dl_handle, PQfinish); -+ LOOKUP_SYMBOL(dl_handle, PQexec); -+ LOOKUP_SYMBOL(dl_handle, PQresultStatus); -+ LOOKUP_SYMBOL(dl_handle, PQresultErrorMessage); -+ LOOKUP_SYMBOL(dl_handle, PQclear); -+ LOOKUP_SYMBOL(dl_handle, PQntuples); -+ LOOKUP_SYMBOL(dl_handle, PQgetvalue); -+} -+ -+ -+ -+/* Thi function parses a URL string like pgsql://[user[:passwd]@]hostname[:port]/database. -+ and returns the result in the given parameters. -+ -+ If an optional keyword is not given, the value of the parameter is not changed. -+*/ -+ -+static int db_pgsql_parse_url(char *url, char **host, char **user, char **pass, char **database, unsigned int *port) -+{ -+ char *pos = strchr(url, '@'); -+ if (pos) { -+ *(pos) = 0; -+ *(user) = url; -+ url = pos + 1; -+ -+ pos = strchr(*user, ':'); -+ if (pos) { -+ *(pos) = 0; -+ *(pass) = (pos +1); -+ } -+ } -+ *host = url; -+ pos = strchr(*host, '/'); -+ if (pos) { -+ // Database -+ (*pos) = 0; -+ *database = pos+1; -+ } -+ pos = strchr(*host, ':'); -+ if (pos) { -+ (*pos) = 0; -+ *port = atoi(pos+1); -+ } -+ return DB_OK; -+} -+ -+int db_postgres_open(const char *file, db_conn_p *conn_p) -+{ -+ PGconn *pg_conn; -+ char *host, *user, *pass, *database; -+ unsigned int port = 5432; /* default postgres port */ -+ char *db_url = malloc(strlen(file)+1); -+ char *create_database_statement; -+ char *pg_conn_info; -+ -+ db_postgres_dlopen(); -+ -+ if (db_url == NULL) -+ csync_fatal("No memory for db_url\n"); -+ -+ user = "postgres"; -+ pass = ""; -+ host = "localhost"; -+ database = "csync2"; -+ -+ strcpy(db_url, file); -+ int rc = db_pgsql_parse_url(db_url, &host, &user, &pass, &database, &port); -+ if (rc != DB_OK) -+ return rc; -+ -+ ASPRINTF(&pg_conn_info, "host='%s' user='%s' password='%s' dbname='%s' port=%d", -+ host, user, pass, database, port); -+ -+ pg_conn = f.PQconnectdb_fn(pg_conn_info); -+ if (pg_conn == NULL) -+ csync_fatal("No memory for postgress connection handle\n"); -+ -+ if (f.PQstatus_fn(pg_conn) != CONNECTION_OK) { -+ f.PQfinish_fn(pg_conn); -+ free(pg_conn_info); -+ -+ ASPRINTF(&pg_conn_info, "host='%s' user='%s' password='%s' dbname='postgres' port=%d", -+ host, user, pass, port); -+ -+ pg_conn = f.PQconnectdb_fn(pg_conn_info); -+ if (pg_conn == NULL) -+ csync_fatal("No memory for postgress connection handle\n"); -+ -+ if (f.PQstatus_fn(pg_conn) != CONNECTION_OK) { -+ csync_debug(0, "Connection failed: %s", f.PQerrorMessage_fn(pg_conn)); -+ f.PQfinish_fn(pg_conn); -+ free(pg_conn_info); -+ return DB_ERROR; -+ } else { -+ char *create_database_statement; -+ PGresult *res; -+ -+ csync_debug(1, "Database %s not found, trying to create it ...", database); -+ ASPRINTF(&create_database_statement, "create database %s", database); -+ res = f.PQexec_fn(pg_conn, create_database_statement); -+ -+ free(create_database_statement); -+ -+ switch (f.PQresultStatus_fn(res)) { -+ case PGRES_COMMAND_OK: -+ case PGRES_TUPLES_OK: -+ break; -+ -+ default: -+ csync_debug(0, "Could not create database %s: %s", database, f.PQerrorMessage_fn(pg_conn)); -+ return DB_ERROR; -+ } -+ -+ f.PQfinish_fn(pg_conn); -+ free(pg_conn_info); -+ -+ ASPRINTF(&pg_conn_info, "host='%s' user='%s' password='%s' dbname='%s' port=%d", -+ host, user, pass, database, port); -+ -+ pg_conn = f.PQconnectdb_fn(pg_conn_info); -+ if (pg_conn == NULL) -+ csync_fatal("No memory for postgress connection handle\n"); -+ -+ if (f.PQstatus_fn(pg_conn) != CONNECTION_OK) { -+ csync_debug(0, "Connection failed: %s", f.PQerrorMessage_fn(pg_conn)); -+ f.PQfinish_fn(pg_conn); -+ free(pg_conn_info); -+ return DB_ERROR; -+ } -+ } -+ } -+ -+ db_conn_p conn = calloc(1, sizeof(*conn)); -+ -+ if (conn == NULL) -+ csync_fatal("No memory for conn\n"); -+ -+ *conn_p = conn; -+ conn->private = pg_conn; -+ conn->close = db_postgres_close; -+ conn->exec = db_postgres_exec; -+ conn->errmsg = db_postgres_errmsg; -+ conn->prepare = db_postgres_prepare; -+ conn->upgrade_to_schema = db_postgres_upgrade_to_schema; -+ -+ free(pg_conn_info); -+ -+ return DB_OK; -+} -+ -+ -+void db_postgres_close(db_conn_p conn) -+{ -+ if (!conn) -+ return; -+ if (!conn->private) -+ return; -+ f.PQfinish_fn(conn->private); -+ conn->private = 0; -+} -+ -+const char *db_postgres_errmsg(db_conn_p conn) -+{ -+ if (!conn) -+ return "(no connection)"; -+ if (!conn->private) -+ return "(no private data in conn)"; -+ return f.PQerrorMessage_fn(conn->private); -+} -+ -+ -+int db_postgres_exec(db_conn_p conn, const char *sql) -+{ -+ PGresult *res; -+ -+ if (!conn) -+ return DB_NO_CONNECTION; -+ -+ if (!conn->private) { -+ /* added error element */ -+ return DB_NO_CONNECTION_REAL; -+ } -+ res = f.PQexec_fn(conn->private, sql); -+ switch (f.PQresultStatus_fn(res)) { -+ case PGRES_COMMAND_OK: -+ case PGRES_TUPLES_OK: -+ return DB_OK; -+ -+ default: -+ return DB_ERROR; -+ } -+} -+ -+ -+int db_postgres_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, -+ char **pptail) -+{ -+ PGresult *result; -+ int *row_p; -+ -+ *stmt_p = NULL; -+ -+ if (!conn) -+ return DB_NO_CONNECTION; -+ -+ if (!conn->private) { -+ /* added error element */ -+ return DB_NO_CONNECTION_REAL; -+ } -+ result = f.PQexec_fn(conn->private, sql); -+ -+ if (result == NULL) -+ csync_fatal("No memory for result\n"); -+ -+ switch (f.PQresultStatus_fn(result)) { -+ case PGRES_COMMAND_OK: -+ case PGRES_TUPLES_OK: -+ break; -+ -+ default: -+ csync_debug(1, "Error in PQexec: %s", f.PQresultErrorMessage_fn(result)); -+ f.PQclear_fn(result); -+ return DB_ERROR; -+ } -+ -+ row_p = malloc(sizeof(*row_p)); -+ if (row_p == NULL) -+ csync_fatal("No memory for row\n"); -+ *row_p = -1; -+ -+ db_stmt_p stmt = malloc(sizeof(*stmt)); -+ if (stmt == NULL) -+ csync_fatal("No memory for stmt\n"); -+ -+ stmt->private = result; -+ stmt->private2 = row_p; -+ -+ *stmt_p = stmt; -+ stmt->get_column_text = db_postgres_stmt_get_column_text; -+ stmt->get_column_blob = db_postgres_stmt_get_column_blob; -+ stmt->get_column_int = db_postgres_stmt_get_column_int; -+ stmt->next = db_postgres_stmt_next; -+ stmt->close = db_postgres_stmt_close; -+ stmt->db = conn; -+ return DB_OK; -+} -+ -+ -+const void* db_postgres_stmt_get_column_blob(db_stmt_p stmt, int column) -+{ -+ PGresult *result; -+ int *row_p; -+ -+ if (!stmt || !stmt->private || !stmt->private2) { -+ return 0; -+ } -+ result = (PGresult*)stmt->private; -+ row_p = (int*)stmt->private2; -+ -+ if (*row_p >= f.PQntuples_fn(result) || *row_p < 0) { -+ csync_debug(1, "row index out of range (should be between 0 and %d, is %d)\n", -+ *row_p, f.PQntuples_fn(result)); -+ return NULL; -+ } -+ return f.PQgetvalue_fn(result, *row_p, column); -+} -+ -+const char *db_postgres_stmt_get_column_text(db_stmt_p stmt, int column) -+{ -+ PGresult *result; -+ int *row_p; -+ -+ if (!stmt || !stmt->private || !stmt->private2) { -+ return 0; -+ } -+ result = (PGresult*)stmt->private; -+ row_p = (int*)stmt->private2; -+ -+ if (*row_p >= f.PQntuples_fn(result) || *row_p < 0) { -+ csync_debug(1, "row index out of range (should be between 0 and %d, is %d)\n", -+ *row_p, f.PQntuples_fn(result)); -+ return NULL; -+ } -+ return f.PQgetvalue_fn(result, *row_p, column); -+} -+ -+int db_postgres_stmt_get_column_int(db_stmt_p stmt, int column) -+{ -+ PGresult *result; -+ int *row_p; -+ -+ if (!stmt || !stmt->private || !stmt->private2) { -+ return 0; -+ } -+ result = (PGresult*)stmt->private; -+ row_p = (int*)stmt->private2; -+ -+ if (*row_p >= f.PQntuples_fn(result) || *row_p < 0) { -+ csync_debug(1, "row index out of range (should be between 0 and %d, is %d)\n", -+ *row_p, f.PQntuples_fn(result)); -+ return 0; -+ } -+ return atoi(f.PQgetvalue_fn(result, *row_p, column)); -+} -+ -+ -+int db_postgres_stmt_next(db_stmt_p stmt) -+{ -+ PGresult *result; -+ int *row_p; -+ -+ if (!stmt || !stmt->private || !stmt->private2) { -+ return 0; -+ } -+ result = (PGresult*)stmt->private; -+ row_p = (int*)stmt->private2; -+ -+ (*row_p)++; -+ if (*row_p >= f.PQntuples_fn(result)) -+ return DB_DONE; -+ -+ return DB_ROW; -+} -+ -+int db_postgres_stmt_close(db_stmt_p stmt) -+{ -+ PGresult *res = stmt->private; -+ -+ f.PQclear_fn(res); -+ free(stmt->private2); -+ free(stmt); -+ return DB_OK; -+} -+ -+ -+int db_postgres_upgrade_to_schema(int version) -+{ -+ if (version < 0) -+ return DB_OK; -+ -+ if (version > 0) -+ return DB_ERROR; -+ -+ csync_debug(2, "Upgrading database schema to version %d.\n", version); -+ -+ csync_db_sql("Creating action table", -+"CREATE TABLE action (" -+" filename varchar(255) DEFAULT NULL," -+" command text," -+" logfile text," -+" UNIQUE (filename,command)" -+");"); -+ -+ csync_db_sql("Creating dirty table", -+"CREATE TABLE dirty (" -+" filename varchar(200) DEFAULT NULL," -+" forced int DEFAULT NULL," -+" myname varchar(100) DEFAULT NULL," -+" peername varchar(100) DEFAULT NULL," -+" UNIQUE (filename,peername)" -+");"); -+ -+ csync_db_sql("Creating file table", -+"CREATE TABLE file (" -+" filename varchar(200) DEFAULT NULL," -+" checktxt varchar(200) DEFAULT NULL," -+" UNIQUE (filename)" -+");"); -+ -+ csync_db_sql("Creating hint table", -+"CREATE TABLE hint (" -+" filename varchar(255) DEFAULT NULL," -+" recursive int DEFAULT NULL" -+");"); -+ -+ csync_db_sql("Creating x509_cert table", -+"CREATE TABLE x509_cert (" -+" peername varchar(255) DEFAULT NULL," -+" certdata varchar(255) DEFAULT NULL," -+" UNIQUE (peername)" -+");"); -+ -+ return DB_OK; -+} -+ -+ -+#endif /* HAVE_POSTGRES */ -diff --git a/db_postgres.h b/db_postgres.h -new file mode 100644 -index 0000000..949439e ---- /dev/null -+++ b/db_postgres.h -@@ -0,0 +1,20 @@ -+ -+#ifndef DB_POSTGRES_H -+#define DB_POSTGRES_H -+ -+/* public */ -+int db_postgres_open(const char *file, db_conn_p *conn_p); -+/* Private */ -+void db_postgres_close(db_conn_p db_conn); -+int db_postgres_exec(db_conn_p conn, const char *sql); -+int db_postgres_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, char **pptail); -+const char *db_postgres_errmsg(db_conn_p db_conn); -+ -+int db_postgres_stmt_next(db_stmt_p stmt); -+const void* db_postgres_stmt_get_column_blob(db_stmt_p stmt, int column); -+const char *db_postgres_stmt_get_column_text(db_stmt_p stmt, int column); -+int db_postgres_stmt_get_column_int(db_stmt_p stmt, int column); -+int db_postgres_stmt_close(db_stmt_p stmt); -+int db_postgres_upgrade_to_schema(int version); -+ -+#endif -diff --git a/db_sqlite.c b/db_sqlite.c -new file mode 100644 -index 0000000..81c5c75 ---- /dev/null -+++ b/db_sqlite.c -@@ -0,0 +1,263 @@ -+/* -+ * Copyright (C) 2010 Dennis Schafroth <dennis@schafroth.com>> -+ * Copyright (C) 2010 Johannes Thoma <johannes.thoma@gmx.at> -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation; either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software -+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+ */ -+ -+#include "csync2.h" -+#if defined(HAVE_SQLITE3) -+#include <sqlite3.h> -+#endif -+#include <stdio.h> -+#include <stdarg.h> -+#include <stdlib.h> -+#include <unistd.h> -+#include <signal.h> -+#include <time.h> -+#include "db_api.h" -+#include "db_sqlite.h" -+#include "dl.h" -+ -+#ifndef HAVE_SQLITE3 -+int db_sqlite_open(const char *file, db_conn_p *conn_p) { -+ return DB_ERROR; -+} -+#else -+ -+static struct db_sqlite3_fns { -+ int (*sqlite3_open_fn) (const char*, sqlite3 **); -+ int (*sqlite3_close_fn) (sqlite3 *); -+ const char *(*sqlite3_errmsg_fn) (sqlite3 *); -+ int (*sqlite3_exec_fn) (sqlite3*, const char *, -+ int (*) (void*,int,char**,char**), void*, char **); -+ int (*sqlite3_prepare_v2_fn)(sqlite3 *, const char *, int, -+ sqlite3_stmt **, const char **pzTail); -+ const unsigned char *(*sqlite3_column_text_fn)(sqlite3_stmt*, int); -+ const void *(*sqlite3_column_blob_fn)(sqlite3_stmt*, int); -+ int (*sqlite3_column_int_fn)(sqlite3_stmt*, int); -+ int (*sqlite3_step_fn)(sqlite3_stmt*); -+ int (*sqlite3_finalize_fn)(sqlite3_stmt *); -+} f; -+ -+static void *dl_handle; -+ -+ -+static void db_sqlite3_dlopen(void) -+{ -+ csync_debug(1, "Opening shared library libsqlite3.so\n"); -+ -+ dl_handle = dlopen("libsqlite3.so", RTLD_LAZY); -+ if (dl_handle == NULL) { -+ csync_fatal("Could not open libsqlite3.so: %s\nPlease install sqlite3 client library (libsqlite3) or use other database (postgres, mysql)\n", dlerror()); -+ } -+ csync_debug(1, "Reading symbols from shared library libsqlite3.so\n"); -+ -+ LOOKUP_SYMBOL(dl_handle, sqlite3_open); -+ LOOKUP_SYMBOL(dl_handle, sqlite3_close); -+ LOOKUP_SYMBOL(dl_handle, sqlite3_errmsg); -+ LOOKUP_SYMBOL(dl_handle, sqlite3_exec); -+ LOOKUP_SYMBOL(dl_handle, sqlite3_prepare_v2); -+ LOOKUP_SYMBOL(dl_handle, sqlite3_column_text); -+ LOOKUP_SYMBOL(dl_handle, sqlite3_column_blob); -+ LOOKUP_SYMBOL(dl_handle, sqlite3_column_int); -+ LOOKUP_SYMBOL(dl_handle, sqlite3_step); -+ LOOKUP_SYMBOL(dl_handle, sqlite3_finalize); -+} -+ -+static int sqlite_errors[] = { SQLITE_OK, SQLITE_ERROR, SQLITE_BUSY, SQLITE_ROW, SQLITE_DONE, -1 }; -+static int db_errors[] = { DB_OK, DB_ERROR, DB_BUSY, DB_ROW, DB_DONE, -1 }; -+ -+int db_sqlite_error_map(int sqlite_err) { -+ int index; -+ for (index = 0; ; index++) { -+ if (sqlite_errors[index] == -1) -+ return DB_ERROR; -+ if (sqlite_err == sqlite_errors[index]) -+ return db_errors[index]; -+ } -+} -+ -+int db_sqlite_open(const char *file, db_conn_p *conn_p) -+{ -+ sqlite3 *db; -+ -+ db_sqlite3_dlopen(); -+ -+ int rc = f.sqlite3_open_fn(file, &db); -+ if ( rc != SQLITE_OK ) { -+ return db_sqlite_error_map(rc); -+ }; -+ db_conn_p conn = calloc(1, sizeof(*conn)); -+ if (conn == NULL) { -+ return DB_ERROR; -+ } -+ *conn_p = conn; -+ conn->private = db; -+ conn->close = db_sqlite_close; -+ conn->exec = db_sqlite_exec; -+ conn->prepare = db_sqlite_prepare; -+ conn->errmsg = db_sqlite_errmsg; -+ conn->upgrade_to_schema = db_sqlite_upgrade_to_schema; -+ return db_sqlite_error_map(rc); -+} -+ -+void db_sqlite_close(db_conn_p conn) -+{ -+ if (!conn) -+ return; -+ if (!conn->private) -+ return; -+ f.sqlite3_close_fn(conn->private); -+ conn->private = 0; -+} -+ -+const char *db_sqlite_errmsg(db_conn_p conn) -+{ -+ if (!conn) -+ return "(no connection)"; -+ if (!conn->private) -+ return "(no private data in conn)"; -+ return f.sqlite3_errmsg_fn(conn->private); -+} -+ -+int db_sqlite_exec(db_conn_p conn, const char *sql) { -+ int rc; -+ if (!conn) -+ return DB_NO_CONNECTION; -+ -+ if (!conn->private) { -+ /* added error element */ -+ return DB_NO_CONNECTION_REAL; -+ } -+ rc = f.sqlite3_exec_fn(conn->private, sql, 0, 0, 0); -+ return db_sqlite_error_map(rc); -+} -+ -+int db_sqlite_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, char **pptail) { -+ int rc; -+ -+ *stmt_p = NULL; -+ -+ if (!conn) -+ return DB_NO_CONNECTION; -+ -+ if (!conn->private) { -+ /* added error element */ -+ return DB_NO_CONNECTION_REAL; -+ } -+ db_stmt_p stmt = malloc(sizeof(*stmt)); -+ sqlite3_stmt *sqlite_stmt = 0; -+ /* TODO avoid strlen, use configurable limit? */ -+ rc = f.sqlite3_prepare_v2_fn(conn->private, sql, strlen(sql), &sqlite_stmt, (const char **) pptail); -+ if (rc != SQLITE_OK) -+ return db_sqlite_error_map(rc); -+ stmt->private = sqlite_stmt; -+ *stmt_p = stmt; -+ stmt->get_column_text = db_sqlite_stmt_get_column_text; -+ stmt->get_column_blob = db_sqlite_stmt_get_column_blob; -+ stmt->get_column_int = db_sqlite_stmt_get_column_int; -+ stmt->next = db_sqlite_stmt_next; -+ stmt->close = db_sqlite_stmt_close; -+ stmt->db = conn; -+ return db_sqlite_error_map(rc); -+} -+ -+const char *db_sqlite_stmt_get_column_text(db_stmt_p stmt, int column) { -+ if (!stmt || !stmt->private) { -+ return 0; -+ } -+ sqlite3_stmt *sqlite_stmt = stmt->private; -+ const unsigned char *result = f.sqlite3_column_text_fn(sqlite_stmt, column); -+ /* error handling */ -+ return (const char*)result; -+} -+ -+#if defined(HAVE_SQLITE3) -+const void* db_sqlite_stmt_get_column_blob(db_stmt_p stmtx, int col) { -+ sqlite3_stmt *stmt = stmtx->private; -+ return f.sqlite3_column_blob_fn(stmt,col); -+} -+#endif -+ -+ -+ -+int db_sqlite_stmt_get_column_int(db_stmt_p stmt, int column) { -+ sqlite3_stmt *sqlite_stmt = stmt->private; -+ int rc = f.sqlite3_column_int_fn(sqlite_stmt, column); -+ return db_sqlite_error_map(rc); -+} -+ -+ -+int db_sqlite_stmt_next(db_stmt_p stmt) -+{ -+ sqlite3_stmt *sqlite_stmt = stmt->private; -+ int rc = f.sqlite3_step_fn(sqlite_stmt); -+ return db_sqlite_error_map(rc); -+} -+ -+int db_sqlite_stmt_close(db_stmt_p stmt) -+{ -+ sqlite3_stmt *sqlite_stmt = stmt->private; -+ int rc = f.sqlite3_finalize_fn(sqlite_stmt); -+ free(stmt); -+ return db_sqlite_error_map(rc); -+} -+ -+ -+int db_sqlite_upgrade_to_schema(int version) -+{ -+ if (version < 0) -+ return DB_OK; -+ -+ if (version > 0) -+ return DB_ERROR; -+ -+ csync_debug(2, "Upgrading database schema to version %d.\n", version); -+ -+ csync_db_sql("Creating file table", -+ "CREATE TABLE file (" -+ " filename, checktxt," -+ " UNIQUE ( filename ) ON CONFLICT REPLACE" -+ ")"); -+ -+ csync_db_sql("Creating dirty table", -+ "CREATE TABLE dirty (" -+ " filename, forced, myname, peername," -+ " UNIQUE ( filename, peername ) ON CONFLICT IGNORE" -+ ")"); -+ -+ csync_db_sql("Creating hint table", -+ "CREATE TABLE hint (" -+ " filename, recursive," -+ " UNIQUE ( filename, recursive ) ON CONFLICT IGNORE" -+ ")"); -+ -+ csync_db_sql("Creating action table", -+ "CREATE TABLE action (" -+ " filename, command, logfile," -+ " UNIQUE ( filename, command ) ON CONFLICT IGNORE" -+ ")"); -+ -+ csync_db_sql("Creating x509_cert table", -+ "CREATE TABLE x509_cert (" -+ " peername, certdata," -+ " UNIQUE ( peername ) ON CONFLICT IGNORE" -+ ")"); -+ -+ return DB_OK; -+} -+ -+#endif -diff --git a/db_sqlite.h b/db_sqlite.h -new file mode 100644 -index 0000000..f5e2340 ---- /dev/null -+++ b/db_sqlite.h -@@ -0,0 +1,19 @@ -+ -+#ifndef DB_SQLITE_H -+#define DB_SQLITE_H -+ -+/* public */ -+int db_sqlite_open(const char *file, db_conn_p *conn_p); -+/* Private */ -+void db_sqlite_close(db_conn_p db_conn); -+int db_sqlite_exec(db_conn_p conn, const char *sql); -+int db_sqlite_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, char **pptail); -+int db_sqlite_stmt_next(db_stmt_p stmt); -+const char* db_sqlite_stmt_get_column_text(db_stmt_p stmt, int column); -+const void* db_sqlite_stmt_get_column_blob(db_stmt_p stmt, int column); -+int db_sqlite_stmt_get_column_int(db_stmt_p stmt, int column); -+int db_sqlite_stmt_close(db_stmt_p stmt); -+const char *db_sqlite_errmsg(db_conn_p conn); -+int db_sqlite_upgrade_to_schema(int version); -+ -+#endif -diff --git a/db_sqlite2.c b/db_sqlite2.c -new file mode 100644 -index 0000000..8b2c85e ---- /dev/null -+++ b/db_sqlite2.c -@@ -0,0 +1,257 @@ -+/* -+ * Copyright (C) 2010 Dennis Schafroth <dennis@schafroth.com>> -+ * Copyright (C) 2010 Johannes Thoma <johannes.thoma@gmx.at> -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation; either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software -+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+ */ -+ -+#include "db_api.h" -+#include "config.h" -+ -+#ifndef HAVE_SQLITE -+/* dummy function to implement a open that fails */ -+int db_sqlite2_open(const char *file, db_conn_p *conn_p) { -+ return DB_ERROR; -+} -+#else -+ -+#include <sqlite.h> -+#include <stdio.h> -+#include <stdarg.h> -+#include <stdlib.h> -+#include <unistd.h> -+#include <signal.h> -+#include <time.h> -+#include "db_sqlite2.h" -+#include <dl.h> -+ -+ -+static struct db_sqlite_fns { -+ sqlite *(*sqlite_open_fn)(const char *, int, char**); -+ void (*sqlite_close_fn)(sqlite *); -+ int (*sqlite_exec_fn)(sqlite *, char *, int (*)(void*,int,char**,char**), void *, char **); -+ int (*sqlite_compile_fn)(sqlite *, const char *, const char **, sqlite_vm **, char **); -+ int (*sqlite_step_fn)(sqlite_vm *, int *, const char ***, const char ***); -+ int (*sqlite_finalize_fn)(sqlite_vm *, char **); -+} f; -+ -+static char *errmsg; -+ -+static void *dl_handle; -+ -+ -+static void db_sqlite_dlopen(void) -+{ -+ csync_debug(1, "Opening shared library libsqlite.so\n"); -+ -+ dl_handle = dlopen("libsqlite.so", RTLD_LAZY); -+ if (dl_handle == NULL) { -+ csync_debug(1, "Libsqlite.so not found, trying libsqlite.so.0\n"); -+ dl_handle = dlopen("libsqlite.so.0", RTLD_LAZY); -+ if (dl_handle == NULL) { -+ csync_fatal("Could not open libsqlite.so: %s\nPlease install sqlite client library (libsqlite) or use other database (postgres, mysql)\n", dlerror()); -+ } -+ } -+ csync_debug(1, "Opening shared library libsqlite.so\n"); -+ -+ LOOKUP_SYMBOL(dl_handle, sqlite_open); -+ LOOKUP_SYMBOL(dl_handle, sqlite_close); -+ LOOKUP_SYMBOL(dl_handle, sqlite_exec); -+ LOOKUP_SYMBOL(dl_handle, sqlite_compile); -+ LOOKUP_SYMBOL(dl_handle, sqlite_step); -+ LOOKUP_SYMBOL(dl_handle, sqlite_finalize); -+ -+} -+ -+ -+int db_sqlite2_open(const char *file, db_conn_p *conn_p) -+{ -+ db_sqlite_dlopen(); -+ -+ sqlite *db = f.sqlite_open_fn(file, 0, &errmsg); -+ if ( db == 0 ) { -+ return DB_ERROR; -+ }; -+ db_conn_p conn = calloc(1, sizeof(*conn)); -+ if (conn == NULL) { -+ return DB_ERROR; -+ } -+ *conn_p = conn; -+ conn->private = db; -+ conn->close = db_sqlite2_close; -+ conn->exec = db_sqlite2_exec; -+ conn->prepare = db_sqlite2_prepare; -+ conn->errmsg = NULL; -+ conn->upgrade_to_schema = db_sqlite2_upgrade_to_schema; -+ return DB_OK; -+} -+ -+void db_sqlite2_close(db_conn_p conn) -+{ -+ if (!conn) -+ return; -+ if (!conn->private) -+ return; -+ f.sqlite_close_fn(conn->private); -+ conn->private = 0; -+} -+ -+const char *db_sqlite2_errmsg(db_conn_p conn) -+{ -+ if (!conn) -+ return "(no connection)"; -+ if (!conn->private) -+ return "(no private data in conn)"; -+ return errmsg; -+} -+ -+int db_sqlite2_exec(db_conn_p conn, const char *sql) { -+ int rc; -+ if (!conn) -+ return DB_NO_CONNECTION; -+ -+ if (!conn->private) { -+ /* added error element */ -+ return DB_NO_CONNECTION_REAL; -+ } -+ rc = f.sqlite_exec_fn(conn->private, (char*) sql, 0, 0, &errmsg); -+ /* On error parse, create DB ERROR element */ -+ return rc; -+} -+ -+int db_sqlite2_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, char **pptail) { -+ int rc; -+ sqlite *db; -+ -+ *stmt_p = NULL; -+ -+ if (!conn) -+ return DB_NO_CONNECTION; -+ -+ if (!conn->private) { -+ /* added error element */ -+ return DB_NO_CONNECTION_REAL; -+ } -+ db = conn->private; -+ -+ db_stmt_p stmt = malloc(sizeof(*stmt)); -+ sqlite_vm *sqlite_stmt = 0; -+ rc = f.sqlite_compile_fn(db, sql, 0, &sqlite_stmt, &errmsg); -+ if (rc != SQLITE_OK) -+ return 0; -+ stmt->private = sqlite_stmt; -+ *stmt_p = stmt; -+ stmt->get_column_text = db_sqlite2_stmt_get_column_text; -+ stmt->get_column_blob = db_sqlite2_stmt_get_column_blob; -+ stmt->get_column_int = db_sqlite2_stmt_get_column_int; -+ stmt->next = db_sqlite2_stmt_next; -+ stmt->close = db_sqlite2_stmt_close; -+ stmt->db = conn; -+ return DB_OK; -+} -+ -+const char *db_sqlite2_stmt_get_column_text(db_stmt_p stmt, int column) { -+ if (!stmt || !stmt->private) { -+ return 0; -+ } -+ sqlite_vm *sqlite_stmt = stmt->private; -+ const char **values = stmt->private2; -+ return values[column]; -+} -+ -+const void* db_sqlite2_stmt_get_column_blob(db_stmt_p stmt, int col) { -+ return db_sqlite2_stmt_get_column_text(stmt, col); -+} -+ -+int db_sqlite2_stmt_get_column_int(db_stmt_p stmt, int column) { -+ sqlite_vm *sqlite_stmt = stmt->private; -+ const char **values = stmt->private2; -+ const char *str_value = values[column]; -+ int value = 0; -+ if (value) -+ value = atoi(str_value); -+ /* TODO missing way to return error */ -+ return value; -+} -+ -+ -+int db_sqlite2_stmt_next(db_stmt_p stmt) -+{ -+ sqlite_vm *sqlite_stmt = stmt->private; -+ const char **dataSQL_V, **dataSQL_N; -+ const char **values; -+ const char **names; -+ int columns; -+ -+ int rc = f.sqlite_step_fn(sqlite_stmt, &columns, &values, &names); -+ stmt->private2 = values; -+ /* TODO error mapping */ -+ return rc; // == SQLITE_ROW; -+} -+ -+int db_sqlite2_stmt_close(db_stmt_p stmt) -+{ -+ sqlite_vm *sqlite_stmt = stmt->private; -+ int rc = f.sqlite_finalize_fn(sqlite_stmt, &errmsg); -+ free(stmt); -+ return rc; -+} -+ -+ -+int db_sqlite2_upgrade_to_schema(int version) -+{ -+ if (version < 0) -+ return DB_OK; -+ -+ if (version > 0) -+ return DB_ERROR; -+ -+ csync_debug(2, "Upgrading database schema to version %d.\n", version); -+ -+ csync_db_sql("Creating file table", -+ "CREATE TABLE file (" -+ " filename, checktxt," -+ " UNIQUE ( filename ) ON CONFLICT REPLACE" -+ ")"); -+ -+ csync_db_sql("Creating dirty table", -+ "CREATE TABLE dirty (" -+ " filename, forced, myname, peername," -+ " UNIQUE ( filename, peername ) ON CONFLICT IGNORE" -+ ")"); -+ -+ csync_db_sql("Creating hint table", -+ "CREATE TABLE hint (" -+ " filename, recursive," -+ " UNIQUE ( filename, recursive ) ON CONFLICT IGNORE" -+ ")"); -+ -+ csync_db_sql("Creating action table", -+ "CREATE TABLE action (" -+ " filename, command, logfile," -+ " UNIQUE ( filename, command ) ON CONFLICT IGNORE" -+ ")"); -+ -+ csync_db_sql("Creating x509_cert table", -+ "CREATE TABLE x509_cert (" -+ " peername, certdata," -+ " UNIQUE ( peername ) ON CONFLICT IGNORE" -+ ")"); -+ -+ return DB_OK; -+} -+ -+ -+#endif -diff --git a/db_sqlite2.h b/db_sqlite2.h -new file mode 100644 -index 0000000..79336a4 ---- /dev/null -+++ b/db_sqlite2.h -@@ -0,0 +1,18 @@ -+ -+#ifndef DB_SQLITE2_H -+#define DB_SQLITE2_H -+ -+/* public */ -+int db_sqlite2_open(const char *file, db_conn_p *conn_p); -+/* Private, should not be here */ -+void db_sqlite2_close(db_conn_p db_conn); -+int db_sqlite2_exec(db_conn_p conn, const char *sql); -+int db_sqlite2_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, char **pptail); -+int db_sqlite2_stmt_next(db_stmt_p stmt); -+const char* db_sqlite2_stmt_get_column_text(db_stmt_p stmt, int column); -+const void* db_sqlite2_stmt_get_column_blob(db_stmt_p stmt, int column); -+int db_sqlite2_stmt_get_column_int(db_stmt_p stmt, int column); -+int db_sqlite2_stmt_close(db_stmt_p stmt); -+int db_sqlite2_upgrade_to_schema(int version); -+ -+#endif -diff --git a/dl.h b/dl.h -new file mode 100644 -index 0000000..0769b2f ---- /dev/null -+++ b/dl.h -@@ -0,0 +1,12 @@ -+#ifndef DL_H -+#define DL_H -+ -+#include <dlfcn.h> -+ -+#define LOOKUP_SYMBOL(dl_handle, sym) \ -+ f.sym ## _fn = dlsym(dl_handle, #sym); \ -+ if ((f.sym ## _fn) == NULL) { \ -+ csync_fatal ("Could not lookup %s in shared library: %s\n", #sym, dlerror()); \ -+ } -+ -+#endif -diff --git a/doc/csync2_paper.tex b/doc/csync2_paper.tex -new file mode 100644 -index 0000000..00f0de0 ---- /dev/null -+++ b/doc/csync2_paper.tex -@@ -0,0 +1,910 @@ -+\documentclass[a4paper,twocolumn]{article} -+\usepackage{nopageno} -+ -+\usepackage{svn} -+\SVNdate $Date$ -+ -+\def\csync2{{\sc Csync$^{2}$}} -+ -+\begin{document} -+ -+\title{Cluster synchronization with \csync2} -+\author{Clifford Wolf, http://www.clifford.at/} -+\maketitle -+ -+\section{Introduction} -+ -+\csync2 [1] is a tool for asynchronous file synchronization in clusters. -+Asynchronous file synchronization is good for files which are seldom modified - -+such as configuration files or application images - but it is not adequate for -+some other types of data. -+ -+For instance a database with continuous write accesses should be synced -+synchronously in order to ensure the data integrity. But that does not -+automatically mean that synchronous synchronization is better; it simply is -+different and there are many cases where asynchronous synchronization is -+favored over synchronous synchronization. Some pros of asynchronous -+synchronization are: -+ -+{\bf 1.} -+Most asynchronous synchronization tools (including \csync2) are implemented as -+single-shot commands which need to be executed each time in order to run one -+synchronization cycle. Therefore it is possible to test changes on one host -+before deploying them on the others (and also return to the old state if the -+changes turn out to be bogus). -+ -+{\bf 2.} -+The synchronization algorithms are much simpler and thus less error-prone. -+ -+{\bf 3.} -+Asynchronous synchronization tools can be (and usually are) implemented as -+normal user mode programs. Synchronous synchronization tools need to be -+implemented as operating system extensions. Therefore asynchronous tools are -+easier to deploy and more portable. -+ -+{\bf 4.} -+It is much easier to build systems which allow setups with many hosts and -+complex replication rules. -+ -+But most asynchronous synchronization tools are pretty primitive and do not -+even cover a small portion of the issues found in real world environments. -+ -+I have developed \csync2 because I found none of the existing tools for -+asynchronous synchronization satisfying. The development of \csync2 has -+been sponsored by LINBIT Information Technologies [2], the company which also -+sponsors the synchronous block device synchronization toolchain DRBD [3]. -+ -+\hspace{0.2cm} -+ -+Note: I will simply use the term {\it synchronization} instead of the -+semi-oxymoron {\it asynchronous synchronization} in the rest of this paper. -+ -+\subsection{\csync2 features} -+ -+Most synchronization tools are very simple wrappers for remote-copy tools such -+as {\tt rsync} or {\tt scp}. These solutions work well in most cases but -+still leave a big gap for more sophisticated tools such as \csync2. The most -+important features of \csync2 are described in the following sections. -+ -+\subsubsection{Conflict detection} -+ -+\label{confl_detect} -+ -+Most of the trivial synchronization tools just copy the newer file over the -+older one. This can be a very dangerous behavior if the same file has been -+changed on more than one host. \csync2 detects such a situation as a conflict -+and will not synchronize the file. Those conflicts then need to be resolved -+manually by the cluster administrator. -+ -+It is not considered as a conflict by \csync2 when the same change has been -+performed on two hosts (e.g. because it has already been synchronized with -+another tool). -+ -+It is also possible to let \csync2 resolve conflicts automatically for some or -+all files using one of the pre-defined auto-resolve methods. The available -+methods are: {\tt none} (the default behavior), {\tt first} (the host on which -+\csync2 is executed first wins), {\tt younger} and {\tt older} (the younger or -+older file wins), {\tt bigger} and {\tt smaller} (the bigger or smaller file -+wins), {\tt left} and {\tt right} (the host on the left side or the right side -+in the host list wins). -+ -+The {\tt younger}, {\tt older}, {\tt bigger} and {\tt smaller} methods let the -+remote side win the conflict if the file has been removed on the local side. -+ -+\subsubsection{Replicating file removals} -+ -+Many synchronization tools can not synchronize file removals because they can -+not distinguish between the file being removed on one host and being created on -+the other one. So instead of removing the file on the second host they recreate -+it on the first one. -+ -+\csync2 detects file removals as such and can synchronize them correctly. -+ -+\subsubsection{Complex setups} -+ -+Many synchronization tools are strictly designed for two-host-setups. This is -+an inadequate restriction and so \csync2 can handle any number of hosts. -+ -+\csync2 can even handle complex setups where e.g. all hosts in a cluster share -+the {\tt /etc/hosts} file, but one {\tt /etc/passwd} file is only shared among -+the members of a small sub-group of hosts and another {\tt /etc/passwd} file is -+shared among the other hosts in the cluster. -+ -+\subsubsection{Reacting to updates} -+ -+In many cases it is not enough to simply synchronize a file between cluster -+nodes. It also is important to tell the applications using the synchronized -+file that the underlying file has been changed, e.g. by restarting the -+application. -+ -+\csync2 can be configured to execute arbitrary commands when files matching an -+arbitrary set of shell patterns are synchronized. -+ -+\section{The \csync2 algorithm} -+ -+Many other synchronization tools compare the hosts, try to figure out which -+host is the most up-to-date one and then synchronize the state from this host -+to all other hosts. This algorithm can not detect conflicts, can not -+distinguish between file removals and file creations and therfore it is not -+used in \csync2. -+ -+\csync2 creates a little database with filesystem metadata on each host. This -+database ({\tt /var/lib/csync2/{\it hostname}.db}) contains a list of the local -+files under the control of \csync2. The database also contains information such -+as the file modification timestamps and file sizes. -+ -+This database is used by \csync2 to detect changes by comparison with the local -+filesystem. The synchronization itself is then performed using the \csync2 -+protocol (TCP port 30865). -+ -+Note that this approach implies that \csync2 can only push changes from the -+machine on which the changes has been performed to the other machines in the -+cluster. Running \csync2 on any other machine in the cluster can not detect and -+so can not synchronize the changes. -+ -+Librsync [4] is used for bandwidth-saving file synchronization and SSL is used for -+encrypting the network traffic. The sqlite library [5] (version 2) is used for -+managing the \csync2 database files. Authentication is performed using -+auto-generated pre-shared-keys in combination with the peer IP address and -+the peer SSL certificate. -+ -+\section{Setting up \csync2} -+ -+\subsection{Building \csync2 from source} -+ -+Simply download the latest \csync2 source tar.gz from {\bf \tt http://oss.linbit.com/csync2/}, -+extract it and run the usual {\tt ./configure} - {\tt make} - {\tt make install} trio. -+ -+\csync2 has a few prerequisites in addition to a C compiler, the standard -+system libraries and headers and the usual gnu toolchain ({\tt make}, etc): -+ -+{\bf 1.} You need librsync, libsqlite (version 2) and libssl installed -+(including development headers). -+ -+{\bf 2.} Bison and flex are needed to build the configuration file parser. -+ -+\subsection{\csync2 in Linux distributions} -+ -+As of this writing there are no official Debian, RedHat or SuSE packages for -+\csync2. Gentoo has a \csync2 package, but is has not been updated for a year -+now. As far as I know, ROCK Linux [6] is the only system with an up-to-date -+\csync2 package. So I recommend that all users of non-ROCK distributions built -+the package from source. -+ -+The \csync2 source package contains an RPM {\tt .specs} file as well as a {\tt -+debian/} directory. So it is possible to use {\tt rpmbuild} or {\tt debuild} to -+build \csync2. -+ -+\subsection{Post installation} -+ -+Next you need to create an SSL certificate for the local \csync2 server. -+Simply running {\tt make cert} in the \csync2 source directory will create and -+install a self-signed SSL certificate for you. Alternatively, if you have no -+source around, run the following commands: -+ -+\begin{verbatim} -+openssl genrsa \ -+ -out /etc/csync2_ssl_key.pem 1024 -+openssl req -new \ -+ -key /etc/csync2_ssl_key.pem \ -+ -out /etc/csync2_ssl_cert.csr -+openssl x509 -req -days 600 \ -+ -in /etc/csync2_ssl_cert.csr \ -+ -signkey /etc/csync2_ssl_key.pem \ -+ -out /etc/csync2_ssl_cert.pem -+\end{verbatim} -+ -+You have to do that on each host you're running csync2 on. When servers are -+talking with each other for the first time, they add each other to the database. -+ -+The \csync2 TCP port 30865 needs to be added to the {\tt /etc/services} file and -+inetd needs to be told about \csync2 by adding -+ -+\begin{verbatim} -+csync2 stream tcp nowait root \ -+ /usr/local/sbin/csync2 csync2 -i -+\end{verbatim} -+ -+to {\tt /etc/inetd.conf}. -+ -+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -+ -+\begin{figure*}[t] -+ \begin{center} -+\begin{verbatim} -+group mygroup # A synchronization group (see 3.4.1) -+{ -+ host host1 host2 (host3); # host list (see 3.4.2) -+ host host4@host4-eth2; -+ -+ key /etc/csync2.key_mygroup; # pre-shared-key (see 3.4.3) -+ -+ include /etc/apache; # include/exclude patterns (see 3.4.4) -+ include %homedir%/bob; -+ exclude %homedir%/bob/temp; -+ exclude *~ .*; -+ -+ action # an action section (see 3.4.5) -+ { -+ pattern /etc/apache/httpd.conf; -+ pattern /etc/apache/sites-available/*; -+ exec "/usr/sbin/apache2ctl graceful"; -+ logfile "/var/log/csync2_action.log"; -+ do-local; -+ # do-local-only; -+ } -+ -+ backup-directory /var/backups/csync2; -+ backup-generations 3; # backup old files (see 3.4.11) -+ -+ auto none; # auto resolving mode (see 3.4.6) -+} -+ -+prefix homedir # a prefix declaration (see 3.4.7) -+{ -+ on host[12]: /export/users; -+ on *: /home; -+} -+\end{verbatim} -+ \end{center} -+ \caption{Example \csync2 configuration file} -+\end{figure*} -+ -+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -+ -+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -+ -+\begin{figure*}[t] -+ \begin{center} -+\begin{verbatim} -+csync2 -cr / -+if csync2 -M; then -+ echo "!!" -+ echo "!! There are unsynced changes! Type 'yes' if you still want to" -+ echo "!! exit (or press crtl-c) and anything else if you want to start" -+ echo "!! a new login shell instead." -+ echo "!!" -+ if read -p "Do you really want to logout? " in && -+ [ ".$in" != ".yes" ]; then -+ exec bash --login -+ fi -+fi -+\end{verbatim} -+ \end{center} -+ \caption{The {\tt csync2\_locheck.sh} script} -+\end{figure*} -+ -+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -+ -+\subsection{Configuration File} -+ -+Figure 1 shows a simple \csync2 configuration file. The configuration filename -+is {\tt /etc/csync2.cfg} when no {\tt -C {\it configname}} option has been -+passed and {\tt /etc/csync2\_{\it configname}.cfg} with a {\tt -C {\it -+configname}} option. -+ -+\subsubsection{Synchronization Groups} -+ -+In the example configuration file you will find the declaration of a -+synchronization group called {\tt mygroup}. A \csync2 setup can have any number -+of synchronization groups. Each group has its own list of member hosts and -+include/exclude rules. -+ -+\csync2 automatically ignores all groups which do not contain the local -+hostname in the host list. This way you can use one big \csync2 configuration -+file for the entire cluster. -+ -+\subsubsection{Host Lists} -+ -+Host lists are specified using the {\tt host} keyword. You can eighter specify -+the hosts in a whitespace seperated list or use an extra {\tt host} statement -+for each host. -+ -+The hostnames used here must be the local hostnames of the cluster nodes. That -+means you must use exactly the same string as printed out by the {\tt hostname} -+command. Otherwise csync2 would be unable to associate the hostnames in the -+configuration file with the cluster nodes. -+ -+The {\tt -N \it hostname} command line option can be used to set the local -+hostname used by \csync2 to a different value than the one provided by the {\tt -+hostname} command. This may be e.g. useful for environments where the local -+hostnames are automatically set by a DHCP server and because of that change -+often. -+ -+Sometimes it is desired that a host is receiving \csync2 connections on an IP -+address which is not the IP address its DNS entry resolves to, e.g.~when a -+crossover cable is used to directly connect the hosts or an extra -+synchronization network should be used. In this cases the syntax {\tt{\it -+hostname}@{\it interfacename}} has to be used for the {\tt host} records (see -+{\tt host4} in the example config file). -+ -+Sometimes a host shall only receive updates from other hosts in the -+synchronization group but shall not be allowed to send updates to the other -+hosts. Such hosts (so-called {\it slave hosts}) must be specified in -+brackets, such as {\tt host3} in the example config file. -+ -+\subsubsection{Pre-Shared-Keys} -+ -+Authentication is performed using the IP addresses and pre-shared-keys in -+\csync2. Each synchronization group in the config file must have exactly one -+{\tt key} record specifying the file containing the pre-shared-key for this -+group. It is recommended to use a separate key for each synchronization group -+and only place a key file on those hosts which actually are members in the -+corresponding synchronization group. -+ -+The key files can be generated with {\tt csync2 -k {\it filename}}. -+ -+\subsubsection{Include/Exclude Patterns} -+ -+The {\tt include} and {\tt exclude} patterns are used to specify which files -+should be synced in the synchronization group. -+ -+There are two kinds of patterns: pathname patterns which start with a slash -+character (or a prefix such as the {\tt \%homedir\%} in the example; prefixes -+are explained in a later section) and basename patterns which do not. -+ -+The last matching pattern for each of both categories is chosen. If -+both categories match, the file will be synchronized. -+ -+The pathname patterns are matched against the beginning of the filename. So they -+must either match the full absolute filename or must match a directory in the -+path to the file. The file will not be synchronized if no matching {\tt include} or -+{\tt exclude} pathname pattern is found (i.e. the default pathname pattern is -+an exclude pattern). -+ -+The basename patterns are matched against the base filename without the path. So -+they can e.g. be used to include or exclude files by their filename extensions. -+The default basename pattern is an include pattern. -+ -+In our example config file that means that all files from {\tt /etc/apache} and -+{\tt \%homedir\%/bob} are synced, except the dot files, files with a tilde -+character at the end of the filename, and files from {\tt -+\%homedir\%/bob/temp}. -+ -+\subsubsection{Actions} -+ -+Each synchronization group may have any number of {\tt action} sections. These -+{\tt action} sections are used to specify shell commands which should be -+executed after a file is synchronized that matches any of the specified -+patterns. -+ -+The {\tt exec} statement is used to specify the command which should be -+executed. Note that if multiple files matching the pattern are synced in one -+run, this command will only be executed once. The special token {\tt \%\%} in -+the command string is substituted with the list of files which triggered the -+command execution. -+ -+The output of the command is appended to the specified logfile, or to -+{\tt /dev/null} if the {\tt logfile} statement is omitted. -+ -+Usually the action is only triggered on the targed hosts, not on the host on -+which the file modification has been detected in the first place. The {\tt -+do-local} statement can be used to change this behavior and let \csync2 also -+execute the command on the host from which the modification originated. You can -+use {\ttdo-local-only} to execute the action only on this machine. -+ -+\subsubsection{Conflict Auto-resolving} -+ -+The {\tt auto} statement is used to specify the conflict auto-resolving -+mechanism for this synchronization group. The default value is {\tt auto none}. -+ -+See section \ref{confl_detect} for a list of possible values for this setting. -+ -+\subsubsection{Prefix Declarations} -+ -+Prefixes (such as the {\tt \%homedir\%} prefix in the example configuration -+file) can be used to synchronize directories which are named differently on -+the cluster nodes. In the example configuration file the directory for the -+user home directories is {\tt /export/users} on the hosts {\tt host1} and -+{\tt host2} and {\tt /home} on the other hosts. -+ -+The prefix value must be an absolute path name and must not contain any -+wildcard characters. -+ -+\subsubsection{The {\tt nossl} statement} -+ -+Usually all \csync2 network communication is encrypted using SSL. This can be -+changed with the {\tt nossl} statement. This statement may only occur in the -+root context (not in a {\tt group} or {\tt prefix} section) and has two -+parameters. The first one is a shell pattern matching the source DNS name for -+the TCP connection and the second one is a shell pattern matching the -+destination DNS name. -+ -+So if e.g.~a secure synchronization network is used between some hosts and -+all the interface DNS names end with {\tt -sync}, a simple -+ -+\begin{verbatim} -+nossl *-sync *-sync; -+\end{verbatim} -+ -+will disable the encryption overhead on the synchronization network. All other -+traffic will stay SSL encrypted. -+ -+\subsubsection{The {\tt config} statement} -+ -+The {\tt config} statement is nothing more then an include statement and can be -+used to include other config files. This can be used to modularize the -+configuration file. -+ -+\subsubsection{The {\tt ignore} statement} -+ -+The {\tt ignore} statement can be used to tell \csync2 to not check and not sync -+the file user-id, the file group-id and/or the file permissions. The statement -+is only valid in the root context and accepts the parameters {\tt uid}, {\tt -+gid} and {\tt mod} to turn off handling of user-ids, group-ids and file -+permissions. -+ -+\subsubsection{The {\tt tempdir} statement} -+ -+The {\tt tempdir} statement specifies the directory to be used for temporary -+files while receiving data through librsync. Note that internally, csync2 uses -+a wrapper around tempnam(3), so the {\tt TMPDIR} environment variable will be -+considered first, then the directory defined here, and if that does not work out, -+the system default or {\tt /tmp} will be used. -+ -+\subsubsection{The {\tt lock-timeout} statement} -+ -+The {\tt lock-timeout} statement specifies the seconds to wait wor a database lock -+before giving up. Default is 12 seconds. The amount will be slightly randomized -+with a jitter of up to 6 seconds based on the respective process id. -+ -+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -+ -+\begin{figure*}[t] -+ \begin{center} -+\begin{verbatim} -+CREATE TABLE file ( -+ filename, checktxt, -+ UNIQUE ( filename ) ON CONFLICT REPLACE -+); -+ -+CREATE TABLE dirty ( -+ filename, force, myname, peername, -+ UNIQUE ( filename, peername ) ON CONFLICT IGNORE -+); -+ -+CREATE TABLE hint ( -+ filename, recursive, -+ UNIQUE ( filename, recursive ) ON CONFLICT IGNORE -+); -+ -+CREATE TABLE action ( -+ filename, command, logfile, -+ UNIQUE ( filename, command ) ON CONFLICT IGNORE -+); -+ -+CREATE TABLE x509_cert ( -+ peername, certdata, -+ UNIQUE ( peername ) ON CONFLICT IGNORE -+); -+\end{verbatim} -+ \end{center} -+ \caption{The \csync2 database schema} -+\end{figure*} -+ -+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -+ -+\subsubsection{Backing up} -+ -+\csync2 can back up the files it modifies. This may be useful for scenarios -+where one is afraid of accidentally syncing files in the wrong direction. -+ -+The {\tt backup-directory} statement is used to tell \csync2 in which directory -+it should create the backup files and the {\tt backup-generations} statement is -+used to tell \csync2 how many old versions of the files should be kept in the -+backup directory. -+ -+The files in the backup directory are named like the file they back up, with -+all slashes substituted by underscores and a generation counter appended. Note -+that only the file content, not the metadata such as ownership and permissions -+are backed up. -+ -+Per default \csync2 does not back up the files it modifies. The default -+value for {\tt backup-generations} is {\tt 3}. -+ -+\subsection{Activating the Logout Check} -+ -+The \csync2 sources contain a little script called {\tt csync2\_locheck.sh} -+(Figure 2). -+ -+If you copy that script into your {\tt \textasciitilde/.bash\_logout} script -+(or include it using the {\tt source} shell command), the shell will not let -+you log out if there are any unsynced changes. -+ -+\section{Database Schema} -+ -+Figure 3 shows the \csync2 database schema. The database can be accessed using -+the {\tt sqlite} command line shell. All string values are URL encoded in the -+database. -+ -+The {\tt file} table contains a list of all local files under \csync2 control, -+the {\tt checktxt} attribute contains a special string with information about -+file type, size, modification time and more. It looks like this: -+ -+\begin{verbatim} -+v1:mtime=1103471832:mode=33152: -+uid=1001:gid=111:type=reg:size=301 -+\end{verbatim} -+ -+This {\tt checktxt} attribute is used to check if a file has been changed on -+the local host. -+ -+If a local change has been detected, the entry in the {\tt file} table is -+updated and entries in the {\tt dirty} table are created for all peer hosts -+which should be updated. This way the information that a host should be updated -+does not get lost, even if the host in question is unreachable right now. The -+{\tt force} attribute is set to {\tt 0} by default and to {\tt 1} when the -+cluster administrator marks one side as the right one in a synchronization -+conflict. -+ -+The {\tt hint} table is usually not used. In large setups this table can be -+filled by a daemon listening on the inotify API. It is possible to tell \csync2 -+to not check all files it is responsible for but only those which have entries -+in the {\tt hint} table. However, the Linux syscall API is so fast that this -+only makes sense for really huge setups. -+ -+The {\tt action} table is used for scheduling actions. Usually this table is -+empty after \csync2 has been terminated. However, it is possible that \csync2 -+gets interrupted in the middle of the synchronization process. In this case -+the records in the {\tt action} table are processed when \csync2 is executed -+the next time. -+ -+The {\tt x509\_cert} table is used to cache the SSL cetrificates used by the -+other hosts in the csync2 cluster (like the SSH {\tt known\_hosts} file). -+ -+\section{Running \csync2} -+ -+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -+ -+\begin{figure*}[t] -+ \begin{center} -+ \begin{tabular}{|p{0.5\linewidth}|p{0.5\linewidth}|} -+ \hline -+\begin{tiny} -+\begin{verbatim} -+ -+ -+csync2 1.26 - cluster synchronization tool, 2nd generation -+LINBIT Information Technologies GmbH <http://www.linbit.com> -+Copyright (C) 2004, 2005 Clifford Wolf <clifford@clifford.at> -+This program is free software under the terms of the GNU GPL. -+ -+Usage: csync2 [-v..] [-C config-name] \ -+ [-D database-dir] [-N hostname] [-p port] .. -+ -+With file parameters: -+ -h [-r] file.. Add (recursive) hints for check to db -+ -c [-r] file.. Check files and maybe add to dirty db -+ -u [-d] [-r] file.. Updates files if listed in dirty db -+ -f file.. Force this file in sync (resolve conflict) -+ -m file.. Mark files in database as dirty -+ -+Simple mode: -+ -x [-d] [[-r] file..] Run checks for all given files and update -+ remote hosts. -+ -+Without file parameters: -+ -c Check all hints in db and eventually mark files as dirty -+ -u [-d] Update (transfer dirty files to peers and mark as clear) -+ -+ -H List all pending hints from status db -+ -L List all file-entries from status db -+ -M List all dirty files from status db -+ -+ -S myname peername List file-entries from status db for this -+ synchronization pair. -+ -+ -T Test if everything is in sync with all peers. -+ -+ -T filename Test if this file is in sync with all peers. -+ -+ -T myname peername Test if this synchronization pair is in sync. -+ -+ -T myname peer file Test only this file in this sync pair. -+ -+ -TT As -T, but print the unified diffs. -+ -+ The modes -H, -L, -M and -S return 2 if the requested db is empty. -+ The mode -T returns 2 if both hosts are in sync. -+ -+ -i Run in inetd server mode. -+ -ii Run in stand-alone server mode. -+ -iii Run in stand-alone server mode (one connect only). -+ -+ -R Remove files from database which do not match config entries. -+\end{verbatim} -+\end{tiny} -+ -+& -+ -+\begin{tiny} -+\begin{verbatim} -+Modifiers: -+ -r Recursive operation over subdirectories -+ -d Dry-run on all remote update operations -+ -+ -B Do not block everything into big SQL transactions. This -+ slows down csync2 but allows multiple csync2 processes to -+ access the database at the same time. Use e.g. when slow -+ lines are used or huge files are transferred. -+ -+ -A Open database in asynchronous mode. This will cause data -+ corruption if the operating system crashes or the computer -+ loses power. -+ -+ -I Init-run. Use with care and read the documentation first! -+ You usually do not need this option unless you are -+ initializing groups with really large file lists. -+ -+ -X Also add removals to dirty db when doing a -TI run. -+ -U Don't mark all other peers as dirty when doing a -TI run. -+ -+ -G Group1,Group2,Group3,... -+ Only use this groups from config-file. -+ -+ -P peer1,peer1,... -+ Only update this peers (still mark all as dirty). -+ -+ -F Add new entries to dirty database with force flag set. -+ -+ -t Print timestamps to debug output (e.g. for profiling). -+ -+Creating key file: -+ csync2 -k filename -+ -+Csync2 will refuse to do anything when a /etc/csync2.lock file is found. -+\end{verbatim} -+\end{tiny} -+ \tabularnewline -+ \hline -+ \end{tabular} -+ \end{center} -+ \caption{The \csync2 help message} -+\end{figure*} -+ -+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -+ -+Simply calling {\tt csync2} without any additional arguments prints out a -+help message (Figure 4). A more detailed description of the most -+important usage scenarios is given in the next sections. -+ -+\subsection{Just synchronizing the files} -+ -+The command {\tt csync2 -x} (or {\tt csync2 -xv}) checks for local changes and -+tries to synchronize them to the other hosts. The option {\tt -d} (dry-run) can -+be used to do everything but the actual synchronization. -+ -+When you start \csync2 the first time it compares its empty database with the -+filesystem and sees that all files just have been created. It then will try -+to synchronize the files. If the file is not present on the remote hosts it -+will simply be copied to the other host. There also is no problem if the file -+is already present on the remote host and has the same content. But if the -+file already exists on the remote host and has a different content, you -+have your first conflict. -+ -+\subsection{Resolving a conflict} -+ -+When two or more hosts in a \csync2 synchronization group have detected changes -+for the same file we run into a conflict: \csync2 can not know which version is -+the right one (unless an auto-resolving method has been specified in the -+configuration file). The cluster administrator needs to tell \csync2 which -+version is the correct one. This can be done using {\tt \csync2 -f}, e.g.: -+ -+\begin{verbatim} -+# csync2 -x -+While syncing file /etc/hosts: -+ERROR from peer apollo: -+ File is also marked dirty here! -+Finished with 1 errors. -+ -+# csync2 -f /etc/hosts -+# csync2 -xv -+Connecting to host apollo (PLAIN) ... -+Updating /etc/hosts on apollo ... -+Finished with 0 errors. -+\end{verbatim} -+ -+\subsection{Checking without syncing} -+ -+It is also possible to just check the local filesystem without doing any -+connections to remote hosts: {\tt csync2 -cr /} (the {\tt -r} modifier -+tells \csync2 to do a recursive check). -+ -+{\tt csync2 -c} without any additional parameters checks all files listed -+in the {\tt hints} table. -+ -+The command {\tt csync2 -M} can be used to print the list of files marked dirty -+and therfore scheduled for synchronization. -+ -+\subsection{Comparing the hosts} -+ -+The {\tt csync2 -T} command can be used to compare the local database with the -+database of the remote hosts. Note that this command compares the databases and -+not the filesystems - so make sure that the databases are up-to-date on all -+hosts before running {\tt csync2 -T} and run {\tt csync2 -cr /} if you are -+unsure. -+ -+The output of {\tt csync2 -T} is a table with 4 columns: -+ -+{\bf 1.} The type of the found difference: {\tt X} means that the file exists -+on both hosts but is different, {\tt L} that the file is only present on the -+local host and {\tt R} that the file is only present on the remote host. -+ -+{\bf 2.} The local interface DNS name (usually just the local hostname). -+ -+{\bf 3.} The remote interface DNS name (usually just the remote hostname). -+ -+{\bf 4.} The filename. -+ -+The {\tt csync2 -TT {\it filename}} command can be used for displaying unified -+diffs between a local file and the remote hosts. -+ -+\subsection{Bootstrapping large setups} -+ -+The {\tt -I} option is a nice tool for bootstrapping larger \csync2 -+installations on slower networks. In such scenarios one usually wants to -+initially replicate the data using a more efficient way and then use \csync2 to -+synchronize the changes on a regular basis. -+ -+The problem here is that when you start \csync2 the first time it detects a lot -+of newly created files and wants to synchronize them, just to find out that -+they are already in sync with the peers. -+ -+The {\tt -I} option modifies the behavior of {\tt -c} so it only updates the -+{\tt file} table but does not create entries in the {\tt dirty} table. So you -+can simply use {\tt csync2 -cIr /} to initially create the \csync2 database on -+the cluster nodes when you know for sure that the hosts are already in sync. -+ -+The {\tt -I} option may also be used with {\tt -T} to add the detected -+differences to the dirty table and so induce \csync2 to synchronize the local -+status of the files in question to the remote host. -+ -+Usually {\tt -TI} does only schedule local files which do exist to the dirty -+database. That means that it does not induce \csync2 to remove a file on a -+remote host if it does not exist on the local host. That behavior can be -+changed using the {\tt -X} option. -+ -+The files scheduled to be synced by {\tt -TI} are usually scheduled to be -+synced to all peers, not just the one peer which has been used in the {\tt -TI} -+run. This behavior can be changed using the {\tt -U} option. -+ -+\subsection{Cleaning up the database} -+ -+It can happen that old data is left over in the \csync2 database after a -+configuration change (e.g. files and hosts which are not referred anymore -+by the configuration file). Running {\tt csync2 -R} cleans up such old -+entries in the \csync2 database. -+ -+\subsection{Multiple Configurations} -+ -+Sometimes a higher abstracion level than simply having different -+synchronization groups is needed. For such cases it is possible to use multiple -+configuration files (and databases) side by side. -+ -+The additional configurations must have a unique name. The configuration file -+is then named {\tt /etc/csync2\_{\it myname}.cfg} and the database is named -+{\tt /var/lib/csync2/{\it hostname}\_{\it myname}.db}. Accordingly \csync2 must -+be called with the {\tt -C {\it myname}} option. -+ -+But there is no need for multiple \csync2 daemons. The \csync2 protocol allows -+the client to tell the server which configuration should be used for the -+current TCP connection. -+ -+\section{Performance} -+ -+In most cases \csync2 is used for syncing just some (up to a few hundred) system -+configuration files. In these cases all \csync2 calls are processed in less than -+one second, even on slow hardware. So a performance analysis is not interesting -+for these cases but only for setups where a huge amount of files is synced, -+e.g. when syncing entire application images with \csync2. -+ -+A well-founded performance analysis which would allow meaningful comparisons -+with other synchronization tools would be beyond the scope of this paper. -+So here are just some quick and dirty numbers from a production -+2-node cluster (2.40GHz dual-Xeon, 7200 RPM ATA HD, 1 GB Ram). The machines -+had an average load of 0.3 (web and mail) during my tests.. -+ -+I have about 128.000 files (1.7 GB) of Linux kernel sources and object -+files on an ext3 filesystem under \csync2 control on the machines. -+ -+Checking for changes ({\tt csync2 -cr /}) took 13.7 seconds wall clock time, -+9.1 seconds in user mode and 4.1 seconds in kernel mode. The remaining 0.5 -+seconds were spent in other processes. -+ -+Recreating the local database without adding the files to dirty table ({\tt -+csync2 -cIr} after removing the database file) took 28.5 seconds (18.6 sec -+user mode and 2.6 sec kernel mode). -+ -+Comparing the \csync2 databases of both hosts ({\tt csync2 -T}) took 3 seconds -+wall clock time. -+ -+Running {\tt csync2 -u} after adding all 128.000 files took 10 minutes wall -+clock time. That means that \csync2 tried to sync all 128.000 files and then -+recognized that the remote side had already the most up-to-date version of -+the file after comparing the checksums. -+ -+All numbers are the average values of 10 iterations. -+ -+\section{Security Notes} -+ -+As statet earlier, authentication is performed using the peer IP address and a -+pre-shared-key. The traffic is SSL encrypted and the SSL certificate of the -+peer is checked when there has been already an SSL connection to that peer in -+the past (i.e.~the peer certificate is already cached in the database). -+ -+All DNS names used in the \csync2 configuration file (the {\tt host} records) -+should be resolvable via the {\tt /etc/hosts} file to guard against DNS -+spoofing attacks. -+ -+Depending on the list of files being managed by \csync2, an intruder on one of -+the cluster nodes can also modify the files under \csync2 control on the other -+cluster nodes and so might also gain access on them. However, an intruder can -+not modify any other files on the other hosts because \csync2 checks on the -+receiving side if all updates are OK according to the configuration file. -+ -+For sure, an intruder would be able to work around this security checks when -+\csync2 is also used to sync the \csync2 configuration files. -+ -+\csync2 only syncs the standard UNIX permissions (uid, gid and file mode). -+ACLs, Linux ext2fs/ext3fs attributes and other extended filesystem permissions -+are neither synced nor flushed (e.g. if they are set automatically when -+the file is created). -+ -+\section{Alternatives} -+ -+\csync2 is not the only file synchronization tool. Some of the other -+free software file synchronization tools are: -+ -+\subsection{Rsync} -+ -+Rsync [7] is a tool for fast incremental file transfers, but is not a -+synchronization tool in the context of this paper. Actually \csync2 is -+using the rsync algorithm for file transfers. A variety of synchronization -+tools have been written on top of rsync. Most of them are tiny shell scripts. -+ -+\subsection{Unison} -+ -+Unison [8] is using an algorithm similar to the one used by \csync2, but is -+limited to two-host setups. Its focus is on interactive syncs (there even are -+graphical user interfaces) and it is targeting on syncing home directories -+between a laptop and a workstation. Unison is pretty intuitive to use, among -+other things because of its limitations. -+ -+\subsection{Version Control Systems} -+ -+Version control systems such as Subversion [9] can also be used to synchronize -+configuration files or application images. The advantage of version control -+systems is that they can do three way merges and preserve the entire history -+of a repository. The disadvantage is that they are much slower and require more -+disk space than plain synchronization tools. -+ -+\section{References} -+ -+{[1]} \csync2 \\ -+http://oss.linbit.com/csync2/ -+\medskip \\ -+{[2]} LINBIT Information Technologies \\ -+http://www.linbit.com/ -+\medskip \\ -+{[3]} DRBD \\ -+http://www.drbd.org/ -+\medskip \\ -+{[4]} Librsync \\ -+http://librsync.sourceforge.net/ -+\medskip \\ -+{[5]} SQLite \\ -+http://www.sqlite.org/ -+\medskip \\ -+{[6]} ROCK Linux \\ -+http://www.rocklinux.org/ -+\medskip \\ -+{[7]} Rsync \\ -+http://samba.anu.edu.au/rsync/ -+\medskip \\ -+{[8]} Unison \\ -+http://www.cis.upenn.edu/\textasciitilde{}bcpierce/unison/ -+\medskip \\ -+{[9]} Subversion \\ -+http://subversion.tigris.org/ -+ -+\end{document} -diff --git a/error.c b/error.c -index 26f04b2..82f2f3f 100644 ---- a/error.c -+++ b/error.c -@@ -26,6 +26,7 @@ - #include <time.h> - #include <sys/types.h> - #include <unistd.h> -+#include <syslog.h> - - long csync_last_printtime = 0; - FILE *csync_timestamp_out = 0; -@@ -117,20 +118,29 @@ void csync_debug(int lv, const char *fmt, ...) - { - va_list ap; - -- csync_printtime(); -- - if ( csync_debug_level < lv ) return; - -- if (csync_timestamps) -- csync_printtime_prefix(); -+ if (!csync_syslog) { -+ csync_printtime(); -+ -+ if (csync_timestamps) -+ csync_printtime_prefix(); - -- if ( csync_server_child_pid ) -- fprintf(csync_debug_out, "<%d> ", csync_server_child_pid); -- -- va_start(ap, fmt); -- vfprintf(csync_debug_out, fmt, ap); -- va_end(ap); -+ if ( csync_server_child_pid ) -+ fprintf(csync_debug_out, "<%d> ", csync_server_child_pid); - -+ va_start(ap, fmt); -+ vfprintf(csync_debug_out, fmt, ap); -+ va_end(ap); -+ // Good / bad with extra line -+ fprintf(csync_debug_out,"\n"); -+ } -+ else { -+ va_start(ap,fmt); -+ vsyslog(LOG_DEBUG, fmt, ap); -+ va_end(ap); -+ } - csync_messages_printed++; - } - -+/* Test 3 */ -diff --git a/getrealfn.c b/getrealfn.c -index 01d13ce..b2bc0b7 100644 ---- a/getrealfn.c -+++ b/getrealfn.c -@@ -53,8 +53,11 @@ char *getrealfn(const char *filename) - /* make the path absolute */ - if ( *tempfn != '/' ) { - char *t2, *t1 = my_get_current_dir_name(); -- asprintf(&t2, "%s/%s", t1, tempfn); -- free(t1); free(tempfn); tempfn = t2; -+ -+ ASPRINTF(&t2, "%s/%s", t1, tempfn); -+ free(t1); -+ free(tempfn); -+ tempfn = t2; - } - - /* remove leading slashes from tempfn */ -@@ -108,7 +111,7 @@ char *getrealfn(const char *filename) - if ( !chdir(tempfn) ) { - char *t2, *t1 = my_get_current_dir_name(); - if ( st_mark ) { -- asprintf(&t2, "%s/%s", t1, st_mark+1); -+ ASPRINTF(&t2, "%s/%s", t1, st_mark+1); - free(tempfn); free(t1); tempfn = t2; - } else { - free(tempfn); tempfn = t1; -diff --git a/groups.c b/groups.c -index 1ff9a1a..511586e 100644 ---- a/groups.c -+++ b/groups.c -@@ -41,8 +41,9 @@ int match_pattern_list( - matched = 1; - } - } else { -+ int fnm_pathname = p->star_matches_slashes ? 0 : FNM_PATHNAME; - if ( !fnmatch(p->pattern, filename, -- FNM_LEADING_DIR|FNM_PATHNAME) ) { -+ FNM_LEADING_DIR|fnm_pathname) ) { - match_path = p->isinclude; - matched = 1; - } -@@ -91,10 +92,11 @@ int csync_step_into(const char *file) - continue; - if ( (p->pattern[0] == '/' || p->pattern[0] == '%') && p->isinclude ) { - char t[strlen(p->pattern)+1], *l; -+ int fnm_pathname = p->star_matches_slashes ? 0 : FNM_PATHNAME; - strcpy(t, p->pattern); - while ( (l=strrchr(t, '/')) != 0 ) { - *l = 0; -- if ( !fnmatch(t, file, FNM_PATHNAME) ) -+ if ( !fnmatch(t, file, fnm_pathname) ) - return 1; - } - } -diff --git a/prefixsubst.c b/prefixsubst.c -index 6adedd4..d003bb5 100644 ---- a/prefixsubst.c -+++ b/prefixsubst.c -@@ -46,7 +46,7 @@ const char *prefixsubst(const char *in) - ringbuff_counter = (ringbuff_counter+1) % RINGBUFF_LEN; - if (ringbuff[ringbuff_counter]) - free(ringbuff[ringbuff_counter]); -- asprintf(&ringbuff[ringbuff_counter], "%s%s", p->path, path); -+ ASPRINTF(&ringbuff[ringbuff_counter], "%s%s", p->path, path); - return ringbuff[ringbuff_counter]; - } - } -@@ -56,3 +56,35 @@ const char *prefixsubst(const char *in) - return 0; - } - -+const char *prefixencode(const char *filename) { -+#if __CYGWIN__ -+ if (!strcmp(filename, "/")) { -+ filename = "/cygdrive"; -+ } -+#endif -+ struct csync_prefix *p = csync_prefix; -+ -+ /* -+ * Canonicalized paths will always contain / -+ * Prefixsubsted paths will probably contain % -+ */ -+ if (*filename == '/') -+ while (p) { -+ if (p->path) { -+ int p_len = strlen(p->path); -+ int f_len = strlen(filename); -+ -+ if (p_len <= f_len && !strncmp(p->path, filename, p_len) && -+ (filename[p_len] == '/' || !filename[p_len])) { -+ ringbuff_counter = (ringbuff_counter+1) % RINGBUFF_LEN; -+ if (ringbuff[ringbuff_counter]) -+ free(ringbuff[ringbuff_counter]); -+ ASPRINTF(&ringbuff[ringbuff_counter], "%%%s%%%s", p->name, filename+p_len); -+ return ringbuff[ringbuff_counter]; -+ } -+ } -+ p = p->next; -+ } -+ return filename; -+} -+ -diff --git a/release.sh b/release.sh -new file mode 100755 -index 0000000..8ee447c ---- /dev/null -+++ b/release.sh -@@ -0,0 +1,69 @@ -+#!/bin/bash -+# -+# csync2 - cluster synchronization tool, 2nd generation -+# LINBIT Information Technologies GmbH <http://www.linbit.com> -+# Copyright (C) 2004, 2005 Clifford Wolf <clifford@clifford.at> -+# -+# This program is free software; you can redistribute it and/or modify -+# it under the terms of the GNU General Public License as published by -+# the Free Software Foundation; either version 2 of the License, or -+# (at your option) any later version. -+# -+# This program is distributed in the hope that it will be useful, -+# but WITHOUT ANY WARRANTY; without even the implied warranty of -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+# GNU General Public License for more details. -+# -+# You should have received a copy of the GNU General Public License -+# along with this program; if not, write to the Free Software -+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+# -+# -+# Internal script for tagging a release in subversion and creating -+# the source tar file. -+ -+PACKAGE=csync2 -+URL=http://svn.linbit.com/csync2 -+ -+case "$1" in -+ -*) -+ echo "Usage: $0 newversion" -+ ;; -+ '') -+ svn ls $URL/tags | tr -d / | perl -pe '$x=$_; $x=~s/\n/\t/; print $x; -+ s/(\d+)/sprintf"%04d",$1/eg;' | sort -k2 | cut -f1 -+ ;; -+ *) -+ VERSION=$1 -+ set -ex -+ -+ date "+csync2 ($VERSION-1) unstable; urgency=low%n%n` -+ ` * New Upstream Version.%n%n -- Clifford Wolf ` -+ `<clifford.wolf@linbit.com> %a, %d %b %Y ` -+ `%H:%M:%S %z%n" > debian/changelog.new -+ cat debian/changelog >> debian/changelog.new -+ mv debian/changelog.new debian/changelog -+ svn commit -m "Added version $VERSION to debian changelog." \ -+ debian/changelog -+ -+ svn cp -m "Tagged version $VERSION" \ -+ $URL/trunk $URL/tags/$PACKAGE-$VERSION -+ svn co $URL/tags/$PACKAGE-$VERSION ../$PACKAGE-$VERSION -+ -+ cd ../$PACKAGE-$VERSION -+ svn rm release.sh copycheck.sh -+ perl -pi -e "s/SNAPSHOT/$VERSION/g" configure.ac -+ perl -pi -e "s/SNAPSHOT/$VERSION/g" csync2.spec -+ svn commit -m "Fixed version info in tag $VERSION" -+ -+ sleep 2 -+ wget -O paper.pdf http://www.clifford.at/papers/2005/csync2/paper.pdf -+ ./autogen.sh; rm -rf autom4te.cache debian/ $( find -name .svn ) -+ -+ cd .. -+ tar cvzf $PACKAGE-$VERSION.tar.gz \ -+ --owner=0 --group=0 $PACKAGE-$VERSION -+ rm -rf $PACKAGE-$VERSION -+ ;; -+esac -+ -diff --git a/rsync.c b/rsync.c -index e4a918c..86482ee 100644 ---- a/rsync.c -+++ b/rsync.c -@@ -25,10 +25,188 @@ - #include <errno.h> - #include <stdio.h> - -+/* for tmpfile replacement: */ -+#include <sys/types.h> -+#include <sys/stat.h> -+#include <fcntl.h> -+ -+/* for MAXPATHLEN */ -+#include <sys/param.h> -+ -+ - #ifdef __CYGWIN__ - #include <w32api/windows.h> - #endif - -+ -+/* This has been taken from rsync:lib/compat.c */ -+ -+/** -+ * Like strncpy but does not 0 fill the buffer and always null -+ * terminates. -+ * -+ * @param bufsize is the size of the destination buffer. -+ * -+ * @return index of the terminating byte. -+ **/ -+static size_t strlcpy(char *d, const char *s, size_t bufsize) -+{ -+ size_t len = strlen(s); -+ size_t ret = len; -+ if (bufsize > 0) { -+ if (len >= bufsize) -+ len = bufsize-1; -+ memcpy(d, s, len); -+ d[len] = 0; -+ } -+ return ret; -+} -+ -+ -+/* This has been taken from rsync sources: receiver.c */ -+ -+#define TMPNAME_SUFFIX ".XXXXXX" -+#define TMPNAME_SUFFIX_LEN ((int)sizeof TMPNAME_SUFFIX - 1) -+#define MAX_UNIQUE_NUMBER 999999 -+#define MAX_UNIQUE_LOOP 100 -+ -+/* get_tmpname() - create a tmp filename for a given filename -+ * -+ * If a tmpdir is defined, use that as the directory to put it in. Otherwise, -+ * the tmp filename is in the same directory as the given name. Note that -+ * there may be no directory at all in the given name! -+ * -+ * The tmp filename is basically the given filename with a dot prepended, and -+ * .XXXXXX appended (for mkstemp() to put its unique gunk in). We take care -+ * to not exceed either the MAXPATHLEN or NAME_MAX, especially the last, as -+ * the basename basically becomes 8 characters longer. In such a case, the -+ * original name is shortened sufficiently to make it all fit. -+ * -+ * If the make_unique arg is True, the XXXXXX string is replaced with a unique -+ * string that doesn't exist at the time of the check. This is intended to be -+ * used for creating hard links, symlinks, devices, and special files, since -+ * normal files should be handled by mkstemp() for safety. -+ * -+ * Of course, the only reason the file is based on the original name is to -+ * make it easier to figure out what purpose a temp file is serving when a -+ * transfer is in progress. */ -+ -+static int get_tmpname(char *fnametmp, const char *fname) -+{ -+ int maxname, added, length = 0; -+ const char *f; -+ char *suf; -+ -+ static unsigned counter_limit; -+ unsigned counter; -+ -+ if ((f = strrchr(fname, '/')) != NULL) { -+ ++f; -+ length = f - fname; -+ /* copy up to and including the slash */ -+ strlcpy(fnametmp, fname, length + 1); -+ } else -+ f = fname; -+ fnametmp[length++] = '.'; -+ -+ /* The maxname value is bufsize, and includes space for the '\0'. -+ * NAME_MAX needs an extra -1 for the name's leading dot. */ -+ maxname = MIN(MAXPATHLEN - length - TMPNAME_SUFFIX_LEN, -+ NAME_MAX - 1 - TMPNAME_SUFFIX_LEN); -+ -+ if (maxname < 1) { -+ csync_debug(1, "temporary filename too long: %s\n", fname); -+ fnametmp[0] = '\0'; -+ return 0; -+ } -+ -+ added = strlcpy(fnametmp + length, f, maxname); -+ if (added >= maxname) -+ added = maxname - 1; -+ suf = fnametmp + length + added; -+ -+ if (!counter_limit) { -+ counter_limit = (unsigned)getpid() + MAX_UNIQUE_LOOP; -+ if (counter_limit > MAX_UNIQUE_NUMBER || counter_limit < MAX_UNIQUE_LOOP) -+ counter_limit = MAX_UNIQUE_LOOP; -+ -+ counter = counter_limit - MAX_UNIQUE_LOOP; -+ -+ /* This doesn't have to be very good because we don't need -+ * to worry about someone trying to guess the values: all -+ * a conflict will do is cause a device, special file, hard -+ * link, or symlink to fail to be created. Also: avoid -+ * using mktemp() due to gcc's annoying warning. */ -+ while (1) { -+ snprintf(suf, TMPNAME_SUFFIX_LEN+1, ".%d", counter); -+ if (access(fnametmp, 0) < 0) -+ break; -+ if (++counter >= counter_limit) -+ return 0; -+ } -+ } else -+ memcpy(suf, TMPNAME_SUFFIX, TMPNAME_SUFFIX_LEN+1); -+ -+ return 1; -+} -+ -+ -+/* Returns open file handle for a temp file that resides in the -+ same directory as file fname. The file must be removed after -+ usage. -+*/ -+ -+static FILE *open_temp_file(char *fnametmp, const char *fname) -+{ -+ FILE *f; -+ int fd; -+ -+ if (get_tmpname(fnametmp, fname) == 0) { -+ csync_debug(1, "ERROR: Couldn't find tempname for file %s\n", fname); -+ return NULL; -+ } -+ -+ f = NULL; -+ fd = open(fnametmp, O_CREAT | O_EXCL | O_RDWR, S_IWUSR | S_IRUSR); -+ if (fd >= 0) { -+ f = fdopen(fd, "wb+"); -+ /* not unlinking since rename wouldn't work then */ -+ } -+ if (fd < 0 || !f) { -+ csync_debug(1, "ERROR: Could not open result from tempnam(%s)!\n", fnametmp); -+ return NULL; -+ } -+ -+ return f; -+} -+ -+ -+ -+#ifdef _SVID_SOURCE -+static FILE *paranoid_tmpfile() -+{ -+ char *name; -+ FILE *f; -+ int fd; -+ -+ name = tempnam(csync_tempdir, "csync2"); -+ if (!name) -+ csync_fatal("ERROR: tempnam() didn't return a valid filename!\n"); -+ -+ f = NULL; -+ fd = open(name, O_CREAT | O_EXCL | O_RDWR, S_IWUSR | S_IRUSR); -+ if (fd >= 0) { -+ f = fdopen(fd, "wb+"); -+ unlink(name); -+ } -+ if (fd < 0 || !f) -+ csync_fatal("ERROR: Could not open result from tempnam(%s)!\n", name); -+ -+ csync_debug(3, "Tempfilename is %s\n", name); -+ free(name); -+ return f; -+} -+#else - static FILE *paranoid_tmpfile() - { - FILE *f; -@@ -41,6 +219,7 @@ static FILE *paranoid_tmpfile() - - return f; - } -+#endif - - void csync_send_file(FILE *in) - { -@@ -119,18 +298,23 @@ int csync_rs_check(const char *filename, int isreg) - rs_stats_t stats; - rs_result result; - long size; -+ char tmpfname[MAXPATHLEN]; - - csync_debug(3, "Csync2 / Librsync: csync_rs_check('%s', %d [%s])\n", - filename, isreg, isreg ? "regular file" : "non-regular file"); - - csync_debug(3, "Opening basis_file and sig_file..\n"); - -- sig_file = paranoid_tmpfile(); -+ sig_file = open_temp_file(tmpfname, prefixsubst(filename)); - if ( !sig_file ) goto io_error; -+ if (unlink(tmpfname) < 0) goto io_error; - - basis_file = fopen(prefixsubst(filename), "rb"); -- if ( !basis_file ) basis_file = paranoid_tmpfile(); -- if ( !basis_file ) goto io_error; -+ if ( !basis_file ) { /* ?? why a tmp file? */ -+ basis_file = open_temp_file(tmpfname, prefixsubst(filename)); -+ if ( !basis_file ) goto io_error; -+ if (unlink(tmpfname) < 0) goto io_error; -+ } - - if ( isreg ) { - csync_debug(3, "Running rs_sig_file() from librsync....\n"); -@@ -204,14 +388,19 @@ error:; - - void csync_rs_sig(const char *filename) - { -- FILE *basis_file, *sig_file; -+ FILE *basis_file = 0, *sig_file = 0; - rs_stats_t stats; - rs_result result; -+ char tmpfname[MAXPATHLEN]; - - csync_debug(3, "Csync2 / Librsync: csync_rs_sig('%s')\n", filename); - - csync_debug(3, "Opening basis_file and sig_file..\n"); -- sig_file = paranoid_tmpfile(); -+ -+ sig_file = open_temp_file(tmpfname, prefixsubst(filename)); -+ if ( !sig_file ) goto io_error; -+ if (unlink(tmpfname) < 0) goto io_error; -+ - basis_file = fopen(prefixsubst(filename), "rb"); - if ( !basis_file ) basis_file = fopen("/dev/null", "rb"); - -@@ -227,19 +416,34 @@ void csync_rs_sig(const char *filename) - csync_debug(3, "Signature has been created successfully.\n"); - fclose(basis_file); - fclose(sig_file); -+ -+ return; -+ -+io_error: -+ csync_debug(0, "I/O Error '%s' in rsync-sig: %s\n", -+ strerror(errno), prefixsubst(filename)); -+ -+ if (basis_file) fclose(basis_file); -+ if (sig_file) fclose(sig_file); - } - -+ -+ - int csync_rs_delta(const char *filename) - { -- FILE *sig_file, *new_file, *delta_file; -+ FILE *sig_file = 0, *new_file = 0, *delta_file = 0; - rs_result result; - rs_signature_t *sumset; - rs_stats_t stats; -+ char tmpfname[MAXPATHLEN]; - - csync_debug(3, "Csync2 / Librsync: csync_rs_delta('%s')\n", filename); - - csync_debug(3, "Receiving sig_file from peer..\n"); -- sig_file = paranoid_tmpfile(); -+ sig_file = open_temp_file(tmpfname, prefixsubst(filename)); -+ if ( !sig_file ) goto io_error; -+ if (unlink(tmpfname) < 0) goto io_error; -+ - if ( csync_recv_file(sig_file) ) { - fclose(sig_file); - return -1; -@@ -260,7 +464,10 @@ int csync_rs_delta(const char *filename) - errno = backup_errno; - return -1; - } -- delta_file = paranoid_tmpfile(); -+ -+ delta_file = open_temp_file(tmpfname, prefixsubst(filename)); -+ if ( !delta_file ) goto io_error; -+ if (unlink(tmpfname) < 0) goto io_error; - - csync_debug(3, "Running rs_build_hash_table() from librsync..\n"); - result = rs_build_hash_table(sumset); -@@ -281,6 +488,16 @@ int csync_rs_delta(const char *filename) - fclose(new_file); - - return 0; -+ -+io_error: -+ csync_debug(0, "I/O Error '%s' in rsync-delta: %s\n", -+ strerror(errno), prefixsubst(filename)); -+ -+ if (new_file) fclose(new_file); -+ if (delta_file) fclose(delta_file); -+ if (sig_file) fclose(sig_file); -+ -+ return -1; - } - - int csync_rs_patch(const char *filename) -@@ -289,24 +506,27 @@ int csync_rs_patch(const char *filename) - int backup_errno; - rs_stats_t stats; - rs_result result; -- char buffer[512]; - char *errstr = "?"; -- int rc; -+ char tmpfname[MAXPATHLEN], newfname[MAXPATHLEN]; - - csync_debug(3, "Csync2 / Librsync: csync_rs_patch('%s')\n", filename); - - csync_debug(3, "Receiving delta_file from peer..\n"); -- delta_file = paranoid_tmpfile(); -+ delta_file = open_temp_file(tmpfname, prefixsubst(filename)); - if ( !delta_file ) { errstr="creating delta temp file"; goto io_error; } -+ if (unlink(tmpfname) < 0) { errstr="removing delta temp file"; goto io_error; } - if ( csync_recv_file(delta_file) ) goto error; - - csync_debug(3, "Opening to be patched file on local host..\n"); - basis_file = fopen(prefixsubst(filename), "rb"); -- if ( !basis_file ) basis_file = paranoid_tmpfile(); -- if ( !basis_file ) { errstr="opening data file for reading"; goto io_error; } -+ if ( !basis_file ) { -+ basis_file = open_temp_file(tmpfname, prefixsubst(filename)); -+ if ( !basis_file ) { errstr="opening data file for reading"; goto io_error; } -+ if (unlink(tmpfname) < 0) { errstr="removing data temp file"; goto io_error; } -+ } - - csync_debug(3, "Opening temp file for new data on local host..\n"); -- new_file = paranoid_tmpfile(); -+ new_file = open_temp_file(newfname, prefixsubst(filename)); - if ( !new_file ) { errstr="creating new data temp file"; goto io_error; } - - csync_debug(3, "Running rs_patch_file() from librsync..\n"); -@@ -316,12 +536,12 @@ int csync_rs_patch(const char *filename) - goto error; - } - -- csync_debug(3, "Copying new data to local file..\n"); -+ csync_debug(3, "Renaming tmp file to data file..\n"); - fclose(basis_file); -- rewind(new_file); -- unlink(prefixsubst(filename)); - - #ifdef __CYGWIN__ -+ -+/* TODO: needed? */ - // This creates the file using the native windows API, bypassing - // the cygwin wrappers and so making sure that we do not mess up the - // permissions.. -@@ -350,14 +570,9 @@ int csync_rs_patch(const char *filename) - } - #endif - -- basis_file = fopen(prefixsubst(filename), "wb"); -- if ( !basis_file ) { errstr="opening data file for writing"; goto io_error; } -- -- while ( (rc = fread(buffer, 1, 512, new_file)) > 0 ) -- fwrite(buffer, rc, 1, basis_file); -+ if (rename(newfname, prefixsubst(filename)) < 0) { errstr="renaming tmp file to to be patched file"; goto io_error; } - - csync_debug(3, "File has been patched successfully.\n"); -- fclose(basis_file); - fclose(delta_file); - fclose(new_file); - -diff --git a/update.c b/update.c -index 7c55113..b2c2b85 100644 ---- a/update.c -+++ b/update.c -@@ -44,7 +44,9 @@ int read_conn_status(const char *file, const char *host) - } - if ( file ) - csync_debug(0, "While syncing file %s:\n", file); -- csync_debug(0, "ERROR from peer %s: %s", host, line); -+ else -+ file = "<no file>"; -+ csync_debug(0, "ERROR from peer(%s): %s %s", file, host, line); - csync_error_count++; - return !strcmp(line, "File is also marked dirty here!") ? 1 : 2; - } -@@ -70,7 +72,7 @@ int connect_to_host(const char *peername) - if ( conn_open(peername) ) return -1; - - if ( use_ssl ) { --#if HAVE_LIBGNUTLS_OPENSSL -+#if HAVE_LIBGNUTLS - conn_printf("SSL\n"); - if ( read_conn_status(0, peername) ) { - csync_debug(1, "SSL command failed.\n"); -@@ -196,7 +198,8 @@ auto_resolve_entry_point: - csync_debug(1, "File is already up to date on peer.\n"); - if ( dry_run ) { - printf("?S: %-15s %s\n", peername, filename); -- return; -+ // DS Remove local dirty, even in dry run -+ // return; - } - goto skip_action; - } -@@ -332,7 +335,8 @@ auto_resolve_entry_point: - csync_debug(1, "File is already up to date on peer.\n"); - if ( dry_run ) { - printf("?S: %-15s %s\n", peername, filename); -- return; -+ // DS also skip on dry_run -+ // return; - } - goto skip_action; - } -@@ -540,17 +544,16 @@ void csync_update_host(const char *peername, - struct textlist *tl_mod = 0, **last_tn=&tl; - char *current_name = 0; - struct stat st; -- - SQL_BEGIN("Get files for host from dirty table", -- "SELECT filename, myname, force FROM dirty WHERE peername = '%s' " -+ "SELECT filename, myname, forced FROM dirty WHERE peername = '%s' " - "ORDER by filename ASC", url_encode(peername)) - { -- const char *filename = url_decode(SQL_V[0]); -+ const char *filename = url_decode(SQL_V(0)); - int i, use_this = patnum == 0; - for (i=0; i<patnum && !use_this; i++) - if ( compare_files(filename, patlist[i], recursive) ) use_this = 1; - if (use_this) -- textlist_add2(&tl, filename, url_decode(SQL_V[1]), atoi(SQL_V[2])); -+ textlist_add2(&tl, filename, url_decode(SQL_V(1)), atoi(SQL_V(2))); - } SQL_END; - - /* just return if there are no files to update */ -@@ -558,7 +561,7 @@ void csync_update_host(const char *peername, - - if ( connect_to_host(peername) ) { - csync_error_count++; -- csync_debug(0, "ERROR: Connection to remote host failed.\n"); -+ csync_debug(0, "ERROR: Connection to remote host `%s' failed.\n", peername); - csync_debug(1, "Host stays in dirty state. " - "Try again later...\n"); - return; -@@ -584,6 +587,7 @@ void csync_update_host(const char *peername, - t->next = tl_mod; - tl_mod = t; - } else { -+ csync_debug(3, "Dirty item %s %s %d \n", t->value, t->value2, t->intvalue); - if ( !current_name || strcmp(current_name, t->value2) ) { - conn_printf("HELLO %s\n", url_encode(t->value2)); - if ( read_conn_status(t->value, peername) ) -@@ -600,6 +604,7 @@ ident_failed_1: - - for (t = tl_mod; t != 0; t = t->next) { - if ( !current_name || strcmp(current_name, t->value2) ) { -+ csync_debug(3, "Dirty item %s %s %d ", t->value, t->value2, t->intvalue); - conn_printf("HELLO %s\n", url_encode(t->value2)); - if ( read_conn_status(t->value, peername) ) - goto ident_failed_2; -@@ -624,9 +629,9 @@ void csync_update(const char ** patlist, int patnum, int recursive, int dry_run) - struct textlist *tl = 0, *t; - - SQL_BEGIN("Get hosts from dirty table", -- "SELECT peername FROM dirty GROUP BY peername ORDER BY random()") -+ "SELECT peername FROM dirty GROUP BY peername") - { -- textlist_add(&tl, url_decode(SQL_V[0]), 0); -+ textlist_add(&tl, url_decode(SQL_V(0)), 0); - } SQL_END; - - for (t = tl; t != 0; t = t->next) { -@@ -672,7 +677,7 @@ found_host_check: - - if ( connect_to_host(peername) ) { - csync_error_count++; -- csync_debug(0, "ERROR: Connection to remote host failed.\n"); -+ csync_debug(0, "ERROR: Connection to remote host `%s' failed.\n", peername); - return 0; - } - -@@ -774,7 +779,7 @@ found_host_check: - - if ( connect_to_host(peername) ) { - csync_error_count++; -- csync_debug(0, "ERROR: Connection to remote host failed.\n"); -+ csync_debug(0, "ERROR: Connection to remote host `%s' failed.\n", peername); - return 0; - } - -@@ -798,7 +803,7 @@ found_host: - filename ? url_encode(filename) : "", - filename ? "'" : "") - { -- char *l_file = strdup(url_decode(SQL_V[1])), *l_checktxt = strdup(url_decode(SQL_V[0])); -+ char *l_file = strdup(url_decode(SQL_V(1))), *l_checktxt = strdup(url_decode(SQL_V(0))); - if ( csync_match_file_host(l_file, myname, peername, 0) ) { - if ( remote_eof ) { - got_remote_eof: -@@ -936,17 +941,17 @@ void csync_remove_old() - const struct csync_group *g = 0; - const struct csync_group_host *h; - -- const char *filename = url_decode(SQL_V[0]); -+ const char *filename = url_decode(SQL_V(0)); - - while ((g=csync_find_next(g, filename)) != 0) { -- if (!strcmp(g->myname, SQL_V[1])) -+ if (!strcmp(g->myname, SQL_V(1))) - for (h = g->host; h; h = h->next) { -- if (!strcmp(h->hostname, SQL_V[2])) -+ if (!strcmp(h->hostname, SQL_V(2))) - goto this_dirty_record_is_ok; - } - } - -- textlist_add2(&tl, SQL_V[0], SQL_V[2], 0); -+ textlist_add2(&tl, SQL_V(0), SQL_V(2), 0); - - this_dirty_record_is_ok: - ; -@@ -962,8 +967,8 @@ this_dirty_record_is_ok: - SQL_BEGIN("Query file DB", - "SELECT filename FROM file") - { -- if (!csync_find_next(0, url_decode(SQL_V[0]))) -- textlist_add(&tl, SQL_V[0], 0); -+ if (!csync_find_next(0, url_decode(SQL_V(0)))) -+ textlist_add(&tl, SQL_V(0), 0); - } SQL_END; - for (t = tl; t != 0; t = t->next) { - csync_debug(1, "Removing %s from file db.\n", t->value); diff --git a/testing/csync2/longlong-format.patch b/testing/csync2/longlong-format.patch new file mode 100644 index 0000000000..a37488d7de --- /dev/null +++ b/testing/csync2/longlong-format.patch @@ -0,0 +1,31 @@ +--- old/checktxt.c ++++ new/checktxt.c +@@ -49,7 +49,7 @@ + xxprintf("v1"); + + if ( !S_ISLNK(st->st_mode) && !S_ISDIR(st->st_mode) ) +- xxprintf(":mtime=%Ld", ign_mtime ? (long long)0 : (long long)st->st_mtime); ++ xxprintf(":mtime=%lld", ign_mtime ? (long long)0 : (long long)st->st_mtime); + + if ( !csync_ignore_mod ) + xxprintf(":mode=%d", (int)st->st_mode); +@@ -61,7 +61,7 @@ + xxprintf(":gid=%d", (int)st->st_gid); + + if ( S_ISREG(st->st_mode) ) +- xxprintf(":type=reg:size=%Ld", (long long)st->st_size); ++ xxprintf(":type=reg:size=%lld", (long long)st->st_size); + + if ( S_ISDIR(st->st_mode) ) + xxprintf(":type=dir"); +--- old/update.c ++++ new/update.c +@@ -469,7 +469,7 @@ + + skip_action: + if ( !S_ISLNK(st.st_mode) ) { +- conn_printf("SETIME %s %s %Ld\n", ++ conn_printf("SETIME %s %s %lld\n", + url_encode(key), url_encode(filename), + (long long)st.st_mtime); + last_conn_status = read_conn_status(filename, peername); diff --git a/testing/csync2/rsync-strlcpy-disable.patch b/testing/csync2/rsync-strlcpy-disable.patch new file mode 100644 index 0000000000..5d28e1da29 --- /dev/null +++ b/testing/csync2/rsync-strlcpy-disable.patch @@ -0,0 +1,20 @@ +--- old/rsync.c ++++ new/rsync.c +@@ -48,7 +48,7 @@ + * @param bufsize is the size of the destination buffer. + * + * @return index of the terminating byte. +- **/ ++ * disabled + static size_t strlcpy(char *d, const char *s, size_t bufsize) + { + size_t len = strlen(s); +@@ -61,7 +61,7 @@ + } + return ret; + } +- ++ **/ + /* splits filepath at the last '/', if any, like so: + * dirname basename filepath + * "/" "" "/" |