aboutsummaryrefslogtreecommitdiffstats
path: root/testing/csync2
diff options
context:
space:
mode:
authorNatanael Copa <ncopa@alpinelinux.org>2012-12-18 11:59:01 +0000
committerNatanael Copa <ncopa@alpinelinux.org>2012-12-18 12:03:20 +0000
commit69e2ae91103f30b9c6cd1df326ccb788d8ab7318 (patch)
treed692b04de8670673cd19bc2873c30220880631be /testing/csync2
parent6fc7aa2d17af1ab39ee3255f46e516a851b1c8fb (diff)
downloadaports-69e2ae91103f30b9c6cd1df326ccb788d8ab7318.tar.bz2
aports-69e2ae91103f30b9c6cd1df326ccb788d8ab7318.tar.xz
testing/csync2: refactor the git release
- instead of basing the version on a non released 2.0_rc1, based it on latest release, 1.34 - use date of latest git commit instead of the date it was generated - instead of making a git snapshot, make a patch. This way we don't need upload the source archive any place.
Diffstat (limited to 'testing/csync2')
-rw-r--r--testing/csync2/01-csync2-sqlite3.patch518
-rw-r--r--testing/csync2/02-csync2-1.34-gnutls_pkgconfig.patch24
-rw-r--r--testing/csync2/APKBUILD18
-rw-r--r--testing/csync2/git.patch6640
4 files changed, 6651 insertions, 549 deletions
diff --git a/testing/csync2/01-csync2-sqlite3.patch b/testing/csync2/01-csync2-sqlite3.patch
deleted file mode 100644
index 364dcca792..0000000000
--- a/testing/csync2/01-csync2-sqlite3.patch
+++ /dev/null
@@ -1,518 +0,0 @@
-diff -ruN csync2-1.34.vanilla/action.c csync2-1.34/action.c
---- csync2-1.34.vanilla/action.c 2007-07-24 23:04:18.000000000 +0200
-+++ csync2-1.34/action.c 2008-06-12 15:36:47.000000000 +0200
-@@ -69,7 +69,7 @@
- "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, "%%");
-@@ -130,7 +130,7 @@
- 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 -ruN csync2-1.34.vanilla/check.c csync2-1.34/check.c
---- csync2-1.34.vanilla/check.c 2007-07-24 23:04:18.000000000 +0200
-+++ csync2-1.34/check.c 2008-06-12 15:36:47.000000000 +0200
-@@ -159,7 +159,7 @@
- "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 ( lstat_strict(prefixsubst(filename), &st) != 0 || csync_check_pure(filename) )
- textlist_add(&tl, filename, 0);
- } SQL_END;
-@@ -213,8 +213,9 @@
-
- if ( check_type>0 && lstat_strict(prefixsubst(file), &st) != 0 ) {
- if ( ignnoent ) return 0;
-+ perror("blup");
- csync_fatal("This should not happen: "
-- "Can't stat %s.\n", file);
-+ "Can't stat %s %s %d %d %d.\n", prefixsubst(file), file,stat(file,&st),lstat_strict(file,&st),errno);
- }
-
- switch ( check_type )
-@@ -231,7 +232,7 @@
- "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;
- }
-diff -ruN csync2-1.34.vanilla/config.h.in csync2-1.34/config.h.in
---- csync2-1.34.vanilla/config.h.in 2007-07-24 23:04:24.000000000 +0200
-+++ csync2-1.34/config.h.in 2008-06-12 15:36:47.000000000 +0200
-@@ -6,8 +6,8 @@
- /* Define to 1 if you have the `rsync' library (-lrsync). */
- #undef HAVE_LIBRSYNC
-
--/* Define to 1 if you have the `sqlite' library (-lsqlite). */
--#undef HAVE_LIBSQLITE
-+/* Define to 1 if you have the `sqlite3' library (-lsqlite3). */
-+#undef HAVE_LIBSQLITE3
-
- /* Name of package */
- #undef PACKAGE
-diff -ruN csync2-1.34.vanilla/configure csync2-1.34/configure
---- csync2-1.34.vanilla/configure 2007-07-24 23:04:25.000000000 +0200
-+++ csync2-1.34/configure 2008-06-12 15:36:47.000000000 +0200
-@@ -3671,13 +3671,13 @@
-
- else
-
--{ echo "$as_me:$LINENO: checking for sqlite_exec in -lsqlite" >&5
--echo $ECHO_N "checking for sqlite_exec in -lsqlite... $ECHO_C" >&6; }
--if test "${ac_cv_lib_sqlite_sqlite_exec+set}" = set; then
-+{ echo "$as_me:$LINENO: checking for sqlite3_exec in -lsqlite3" >&5
-+echo $ECHO_N "checking for sqlite3_exec in -lsqlite3... $ECHO_C" >&6; }
-+if test "${ac_cv_lib_sqlite3_sqlite3_exec+set}" = set; then
- echo $ECHO_N "(cached) $ECHO_C" >&6
- else
- ac_check_lib_save_LIBS=$LIBS
--LIBS="-lsqlite $LIBS"
-+LIBS="-lsqlite3 $LIBS"
- cat >conftest.$ac_ext <<_ACEOF
- /* confdefs.h. */
- _ACEOF
-@@ -3691,11 +3691,11 @@
- #ifdef __cplusplus
- extern "C"
- #endif
--char sqlite_exec ();
-+char sqlite3_exec ();
- int
- main ()
- {
--return sqlite_exec ();
-+return sqlite3_exec ();
- ;
- return 0;
- }
-@@ -3718,30 +3718,30 @@
- test ! -s conftest.err
- } && test -s conftest$ac_exeext &&
- $as_test_x conftest$ac_exeext; then
-- ac_cv_lib_sqlite_sqlite_exec=yes
-+ ac_cv_lib_sqlite3_sqlite3_exec=yes
- else
- echo "$as_me: failed program was:" >&5
- sed 's/^/| /' conftest.$ac_ext >&5
-
-- ac_cv_lib_sqlite_sqlite_exec=no
-+ ac_cv_lib_sqlite3_sqlite3_exec=no
- fi
-
- rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
- conftest$ac_exeext conftest.$ac_ext
- LIBS=$ac_check_lib_save_LIBS
- fi
--{ echo "$as_me:$LINENO: result: $ac_cv_lib_sqlite_sqlite_exec" >&5
--echo "${ECHO_T}$ac_cv_lib_sqlite_sqlite_exec" >&6; }
--if test $ac_cv_lib_sqlite_sqlite_exec = yes; then
-+{ echo "$as_me:$LINENO: result: $ac_cv_lib_sqlite3_sqlite3_exec" >&5
-+echo "${ECHO_T}$ac_cv_lib_sqlite3_sqlite3_exec" >&6; }
-+if test $ac_cv_lib_sqlite3_sqlite3_exec = yes; then
- cat >>confdefs.h <<_ACEOF
--#define HAVE_LIBSQLITE 1
-+#define HAVE_LIBSQLITE3 1
- _ACEOF
-
-- LIBS="-lsqlite $LIBS"
-+ LIBS="-lsqlite3 $LIBS"
-
- else
-- { { echo "$as_me:$LINENO: error: libsqlite is required" >&5
--echo "$as_me: error: libsqlite is required" >&2;}
-+ { { echo "$as_me:$LINENO: error: libsqlite3 is required" >&5
-+echo "$as_me: error: libsqlite3 is required" >&2;}
- { (exit 1); exit 1; }; }
- fi
-
-diff -ruN csync2-1.34.vanilla/configure.ac csync2-1.34/configure.ac
---- csync2-1.34.vanilla/configure.ac 2007-07-24 23:04:19.000000000 +0200
-+++ csync2-1.34/configure.ac 2008-06-12 15:36:47.000000000 +0200
-@@ -47,7 +47,7 @@
- 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)])
-+ AC_CHECK_LIB([sqlite3], [sqlite3_exec], , [AC_MSG_ERROR(libsqlite3 is required)])
- )
- AM_CONDITIONAL([PRIVATE_LIBSQLITE], [test -n "$libsqlite_source_file"])
-
-diff -ruN csync2-1.34.vanilla/conn.c csync2-1.34/conn.c
---- csync2-1.34.vanilla/conn.c 2007-07-24 23:04:18.000000000 +0200
-+++ csync2-1.34/conn.c 2008-06-12 15:36:47.000000000 +0200
-@@ -181,7 +181,7 @@
- "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;
-diff -ruN csync2-1.34.vanilla/csync2.c csync2-1.34/csync2.c
---- csync2-1.34.vanilla/csync2.c 2007-07-24 23:04:18.000000000 +0200
-+++ csync2-1.34/csync2.c 2008-06-12 15:36:47.000000000 +0200
-@@ -582,8 +582,8 @@
- 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) {
-@@ -659,7 +659,7 @@
- "SELECT filename FROM file WHERE filename = '%s' %s",
- url_encode(realname), where_rec)
- {
-- char *filename = strdup(url_encode(SQL_V[0]));
-+ char *filename = strdup(url_encode(SQL_V(0)));
- csync_mark(filename, 0, 0);
- free(filename);
- } SQL_END;
-@@ -695,7 +695,7 @@
- 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", SQL_V(0), url_decode(SQL_V(1)));
- retval = -1;
- } SQL_END;
- break;
-@@ -705,8 +705,8 @@
- 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 +717,8 @@
- 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;
-@@ -769,9 +769,9 @@
- SQL_BEGIN("DB Dump - Dirty",
- "SELECT force, 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 -ruN csync2-1.34.vanilla/csync2.h csync2-1.34/csync2.h
---- csync2-1.34.vanilla/csync2.h 2007-07-24 23:04:18.000000000 +0200
-+++ csync2-1.34/csync2.h 2008-06-12 15:36:47.000000000 +0200
-@@ -22,6 +22,7 @@
- #define CSYNC2_H 1
-
- #define _GNU_SOURCE
-+#define _FILE_OFFSET_BITS 64
-
- #include "config.h"
- #include <stdio.h>
-@@ -91,19 +92,24 @@
- 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 void * csync_db_colblob(void *stmtx,int col);
-+
-
- #define SQL(e, s, ...) csync_db_sql(e, s, ##__VA_ARGS__)
-
-+#define SQL_V(col) \
-+ (csync_db_colblob(SQL_VM,col))
-+
- #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 **notSQL_V, **notSQL_N; \
- int SQL_C; \
- if ( !csync_db_next(SQL_VM, SQL_ERR, \
-- &SQL_C, &SQL_V, &SQL_N) ) break; \
-+ &SQL_C, &notSQL_V, &notSQL_N) ) break; \
- SQL_COUNT++;
-
- #define SQL_FIN }{
-diff -ruN csync2-1.34.vanilla/daemon.c csync2-1.34/daemon.c
---- csync2-1.34.vanilla/daemon.c 2007-07-24 23:04:18.000000000 +0200
-+++ csync2-1.34/daemon.c 2008-06-12 15:36:47.000000000 +0200
-@@ -443,8 +443,8 @@
- 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;
-
-diff -ruN csync2-1.34.vanilla/db.c csync2-1.34/db.c
---- csync2-1.34.vanilla/db.c 2007-07-24 23:04:18.000000000 +0200
-+++ csync2-1.34/db.c 2008-06-12 15:36:47.000000000 +0200
-@@ -19,7 +19,7 @@
- */
-
- #include "csync2.h"
--#include <sqlite.h>
-+#include <sqlite3.h>
- #include <stdio.h>
- #include <stdarg.h>
- #include <stdlib.h>
-@@ -33,7 +33,7 @@
- int db_blocking_mode = 1;
- int db_sync_mode = 1;
-
--static sqlite *db = 0;
-+static sqlite3 *db = 0;
-
- static int get_dblock_timeout()
- {
-@@ -128,44 +128,46 @@
-
- void csync_db_open(const char *file)
- {
-- db = sqlite_open(file, 0, 0);
-- if ( db == 0 )
-- csync_fatal("Can't open database: %s\n", file);
-+ int r;
-+ //db = sqlite_open(file, 0, 0);
-+ r=sqlite3_open(file, &db);
-+ if ( db == 0 || r)
-+ csync_fatal("Can't open database: %s, result %d\n", file,r);
-
- /* ignore errors on table creation */
- in_sql_query++;
-- sqlite_exec(db,
-+ sqlite3_exec(db,
- "CREATE TABLE file ("
- " filename, checktxt,"
- " UNIQUE ( filename ) ON CONFLICT REPLACE"
- ")",
- 0, 0, 0);
-- sqlite_exec(db,
-+ sqlite3_exec(db,
- "CREATE TABLE dirty ("
- " filename, force, myname, peername,"
- " UNIQUE ( filename, peername ) ON CONFLICT IGNORE"
- ")",
- 0, 0, 0);
-- sqlite_exec(db,
-+ sqlite3_exec(db,
- "CREATE TABLE hint ("
- " filename, recursive,"
- " UNIQUE ( filename, recursive ) ON CONFLICT IGNORE"
- ")",
- 0, 0, 0);
-- sqlite_exec(db,
-+ sqlite3_exec(db,
- "CREATE TABLE action ("
- " filename, command, logfile,"
- " UNIQUE ( filename, command ) ON CONFLICT IGNORE"
- ")",
- 0, 0, 0);
-- sqlite_exec(db,
-+ sqlite3_exec(db,
- "CREATE TABLE x509_cert ("
- " peername, certdata,"
- " UNIQUE ( peername ) ON CONFLICT IGNORE"
- ")",
- 0, 0, 0);
- if (!db_sync_mode)
-- sqlite_exec(db, "PRAGMA synchronous = OFF", 0, 0, 0);
-+ sqlite3_exec(db, "PRAGMA synchronous = OFF", 0, 0, 0);
- in_sql_query--;
- }
-
-@@ -178,7 +180,7 @@
- SQL("COMMIT TRANSACTION", "COMMIT TRANSACTION");
- tqueries_counter = -10;
- }
-- sqlite_close(db);
-+ sqlite3_close(db);
- begin_commit_recursion--;
- db = 0;
- }
-@@ -199,7 +201,7 @@
- csync_debug(2, "SQL: %s\n", sql);
-
- while (1) {
-- rc = sqlite_exec(db, sql, 0, 0, 0);
-+ rc = sqlite3_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");
-@@ -216,7 +218,8 @@
-
- void* csync_db_begin(const char *err, const char *fmt, ...)
- {
-- sqlite_vm *vm;
-+ //sqlite_vm *vm;
-+ sqlite3_stmt *stmt;
- char *sql;
- va_list ap;
- int rc, busyc = 0;
-@@ -231,7 +234,7 @@
- csync_debug(2, "SQL: %s\n", sql);
-
- while (1) {
-- rc = sqlite_compile(db, sql, 0, &vm, 0);
-+ rc = sqlite3_prepare(db, sql, 0, &stmt, 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");
-@@ -242,19 +245,21 @@
- csync_fatal("Database Error: %s [%d]: %s\n", err, rc, sql);
- free(sql);
-
-- return vm;
-+ return stmt;
- }
-
--int csync_db_next(void *vmx, const char *err,
-+int csync_db_next(void *stmtx, const char *err,
- int *pN, const char ***pazValue, const char ***pazColName)
- {
-- sqlite_vm *vm = vmx;
-+ //sqlite_vm *vm = vmx;
-+ sqlite3_stmt *stmt = stmtx;
- 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);
-+ //rc = sqlite_step(vm, pN, pazValue, pazColName);
-+ rc = sqlite3_step(stmt);
- 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");
-@@ -267,16 +272,21 @@
-
- return rc == SQLITE_ROW;
- }
-+void * csync_db_colblob(void *stmtx,int col) {
-+ sqlite3_stmt *stmt = stmtx;
-+ return sqlite3_column_blob(stmt,col);
-+}
-
--void csync_db_fin(void *vmx, const char *err)
-+void csync_db_fin(void *stmtx, const char *err)
- {
-- sqlite_vm *vm = vmx;
-+ //sqlite_vm *vm = vmx;
-+ sqlite3_stmt *stmt=stmtx;
- int rc, busyc = 0;
-
- csync_debug(2, "SQL Query finished.\n");
-
- while (1) {
-- rc = sqlite_finalize(vm, 0);
-+ rc = sqlite3_finalize(stmt);
- 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");
---- src/csync2-1.34/update.c
-+++ new/update.c
-@@ -545,12 +545,12 @@
- "SELECT filename, myname, force 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 */
-@@ -626,7 +626,7 @@
- SQL_BEGIN("Get hosts from dirty table",
- "SELECT peername FROM dirty GROUP BY peername ORDER BY random()")
- {
-- 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) {
-@@ -798,7 +798,7 @@
- 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 +936,17 @@
- 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 +962,8 @@
- 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/02-csync2-1.34-gnutls_pkgconfig.patch b/testing/csync2/02-csync2-1.34-gnutls_pkgconfig.patch
deleted file mode 100644
index b9e1d2726e..0000000000
--- a/testing/csync2/02-csync2-1.34-gnutls_pkgconfig.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- old/configure.ac
-+++ new/configure.ac
-@@ -59,16 +59,11 @@
- if test "$enable_gnutls" != no
- then
-
-- # 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]]) ])
--
-- # This is a bloody hack for fedora core
-- CFLAGS="$CFLAGS $LIBGNUTLS_CFLAGS"
-- LIBS="$LIBS $LIBGNUTLS_LIBS -ltasn1"
--
-- # 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]])])
--
-+ PKG_CHECK_MODULES(LIBGNUTLS, gnutls, [
-+ CFLAGS="$CFLAGS `$PKG_CONFIG --cflags gnutls libssl`"
-+ LIBS="$LIBS `$PKG_CONFIG --libs gnutls libssl` -DHAVE_LIBGNUTLS_OPENSSL"
-+ ])
-+
- fi
-
- AC_CONFIG_FILES([Makefile])
diff --git a/testing/csync2/APKBUILD b/testing/csync2/APKBUILD
index 6b2ff91982..48199b3051 100644
--- a/testing/csync2/APKBUILD
+++ b/testing/csync2/APKBUILD
@@ -1,6 +1,7 @@
# Maintainer: Natanael Copa <ncopa@alpinelinux.org>
pkgname=csync2
-pkgver=2.0_rc1_git20121210
+pkgver=1.34_git20111201
+_ver=${pkgver%_git*}
pkgrel=0
pkgdesc="Cluster synchronization tool"
url="http://oss.linbit.com/csync2/"
@@ -10,12 +11,13 @@ depends=
makedepends="librsync-dev gnutls-dev sqlite-dev autoconf automake bison flex"
install=
subpackages="$pkgname-doc"
-source="$pkgname-$pkgver.tar.gz
+source="http://oss.linbit.com/csync2/csync2-${_ver}.tar.gz
+ git.patch
03-strlcpy_disable.patch"
# 01-csync2-sqlite3.patch
# 02-csync2-1.34-gnutls_pkgconfig.patch
-_builddir="$srcdir"/$pkgname-$pkgver
+_builddir="$srcdir"/$pkgname-$_ver
_giturl="http://git.linbit.com/csync2.git"
@@ -41,9 +43,10 @@ snapshot() {
prepare() {
cd "$_builddir"
- for i in ../*.patch; do
- msg "Applying $i"
- patch -p1 -i $i || return 1
+ for i in $source; do
+ case $i in
+ *.patch) msg $i; patch -p1 -i "$srcdir"/$i || return 1;;
+ esac
done
autoreconf --install --force
}
@@ -62,5 +65,6 @@ package() {
make DESTDIR="$pkgdir" install
}
-md5sums="9c835a405db0ab3496239ef185bc43f2 csync2-2.0_rc1_git20121210.tar.gz
+md5sums="efc8a3548996b79cef2ad76af5e93cd8 csync2-1.34.tar.gz
+bd4957d25e984518d929ae4036bf0ae9 git.patch
e6d8aaff70cf847b11873cd1cfaaa8f6 03-strlcpy_disable.patch"
diff --git a/testing/csync2/git.patch b/testing/csync2/git.patch
new file mode 100644
index 0000000000..5d9b7094b6
--- /dev/null
+++ b/testing/csync2/git.patch
@@ -0,0 +1,6640 @@
+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);