Add support for logging daemon messages to an SQL database.
-After applying this patch you'll need to run autoconf and autoheader to
-generate updated versions of "configure" and "config.h.in".
+To use this patch, run these commands for a successful build:
-You'll need to run configure with the --with-ODBC option in order for the
-extended features to be active.
+ patch -p1 <patches/ODBC-dblog.diff
+ ./prepare-source
+ ./configure --enable-ODBC
+ make
-Patch provided by Steve Sether.
+See the newly-created file "instructions" for more info.
-(Tweaked by Wayne Davison for rsync-style purposes but not compiled, so if the
-dblog.c file has a compile problem, it's probably my fault...)
-
-
---- /dev/null 1 Jan 1970 00:00:00 -0000
-+++ README-ODBC 2004-04-07 22:42:28.000000000 -0700
-@@ -0,0 +1,80 @@
-+This patch adds the following options:
-+
-+"database logging"
-+If set to True, rsync will attempt to connect to
-+the specified datasource and write to the named tables.
-+Defaults to False.
-+
-+
-+"database datasource"
-+Specifies the name of the ODBC data source to use.
-+
-+"database username"
-+The username to use when connecting to the database.
-+
-+"database password"
-+The password to use when connecting to the database.
-+
-+"transfer table name"
-+The name of the transfer table to log to.
-+This table contains individual filenames, file sizes, bytes transferred, checksum bytes transferred, operation (send or receive), and a timestamp.
-+
-+"session table name"
-+The name of the session table to log to.
-+This table contains the username, module name, module path, ip address, process ID, and a timestamp.
-+
-+"exit table name"
-+The name of the exit table to log to.
-+This table contains the total bytes read, total bytes written, total size
-+of all files, error code, line the error occured at, file the error occured at
-+and the text of the error. (most of which will be blank if the program exited normally).
-+
-+
-+"unique id method"
-+Different databases use different methods to get a unique identifier.
-+Some databases support sequence objects, and use various forms of the
-+nextval command to retrieve a unique identifier from it. Other databases
-+support an autonumber field, and support different methds of retrieving
-+the ID used in the last insert. Valid values for this option are:
-+
-+ nextval-postgres
-+ uses the syntax of nextval for PostgreSQL databases
-+
-+ nextval-oracle
-+ uses the syntax of nextval for Oracle databases
-+
-+ nextval-db2
-+ uses the syntax of nextval for DB2 databases
-+
-+ last_insert_id
-+ uses the last_insert_id() command for the MySQL databases
-+
-+ @@IDENTITY
-+ uses the @@IDENTITY command for Sybase databases
-+
-+ custom
-+ Define your own method to get a unique identifier. See the
-+ "custom unique id select", and the "get custom id before select"
-+ parameters.
-+
-+
-+"sequence name"
-+If your database supports sequences, list the name of the sequence to use
-+for the session unique identifier.
-+
-+"custom unique id select"
-+Only used if you specify the custom method in "unique id method". This
-+is a SQL statement to be executed to get a unique ID. This SQL
-+statement must return one column with the unique ID to use for
-+the session ID. Should be used in concert with the "get custom
-+id before select" parameter.
-+
-+"get custom id before insert"
-+This parameter is ignored unless the "unique id method" is set to custom.
-+If set to true, the "custom unique id select" statement will
-+be executed BEFORE the session row is inserted into the database.
-+(as is done when a sequence is used for unique IDs).
-+If False the statement will be executed after the session
-+row is inserted (as is done when the session ID is automatically generates
-+unique IDs).
-+Defaults to True.
---- Makefile.in 10 Feb 2004 17:06:11 -0000 1.98
-+++ Makefile.in 8 Apr 2004 05:56:31 -0000
-@@ -32,7 +32,7 @@ ZLIBOBJ=zlib/deflate.o zlib/infblock.o z
- zlib/inflate.o zlib/inftrees.o zlib/infutil.o zlib/trees.o \
- zlib/zutil.o zlib/adler32.o
- OBJS1=rsync.o generator.o receiver.o cleanup.o sender.o exclude.o util.o \
-- main.o checksum.o match.o syscall.o log.o backup.o
-+ main.o checksum.o match.o syscall.o log.o backup.o @EXTRA_OBJECT@
- OBJS2=options.o flist.o io.o compat.o hlink.o token.o uidlist.o socket.o \
- fileio.o batch.o clientname.o
+--- old/Makefile.in
++++ new/Makefile.in
+@@ -32,7 +32,7 @@ LIBOBJ=lib/wildmatch.o lib/compat.o lib/
+ ZLIBOBJ=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
+ zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o
+ OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
+- util.o main.o checksum.o match.o syscall.o log.o backup.o
++ util.o main.o checksum.o match.o syscall.o log.o backup.o @EXTRA_OBJECT@
+ OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o \
+ fileio.o batch.o clientname.o chmod.o acls.o xattrs.o
OBJS3=progress.o pipe.o
---- cleanup.c 27 Jan 2004 08:14:33 -0000 1.21
-+++ cleanup.c 8 Apr 2004 05:56:31 -0000
-@@ -138,7 +138,12 @@ void _exit_cleanup(int code, const char
- code = RERR_VANISHED;
- }
+--- old/cleanup.c
++++ new/cleanup.c
+@@ -26,6 +26,7 @@ extern int am_server;
+ extern int am_daemon;
+ extern int io_error;
+ extern int keep_partial;
++extern int am_generator;
+ extern int log_got_error;
+ extern char *partial_dir;
+ extern char *logfile_name;
+@@ -173,8 +174,13 @@ NORETURN void _exit_cleanup(int code, co
+ code = exit_code = RERR_PARTIAL;
+ }
-- if (code) log_exit(code, file, line);
-+ if (code) {
-+ log_exit(code, file, line);
+- if (code || am_daemon || (logfile_name && (am_server || !verbose)))
++ if (code || am_daemon || (logfile_name && (am_server || !verbose))) {
+ log_exit(code, file, line);
+#ifdef HAVE_LIBODBC
-+ db_log_exit(code,file,line);
++ db_log_exit(code, file, line);
++ db_log_close();
+#endif
-+ }
++ }
- if (verbose > 2)
- rprintf(FINFO,"_exit_cleanup(code=%d, file=%s, line=%d): about to call exit(%d)\n",
---- clientserver.c 14 Apr 2004 23:33:34 -0000 1.121
-+++ clientserver.c 15 Apr 2004 18:51:14 -0000
-@@ -311,6 +311,9 @@ static int rsync_module(int f_in, int f_
- exclude_path_prefix = NULL;
+ /* FALLTHROUGH */
+ #include "case_N.h"
+--- old/clientserver.c
++++ new/clientserver.c
+@@ -396,6 +396,9 @@ static int rsync_module(int f_in, int f_
+ XFLG_ABS_IF_SLASH | XFLG_OLD_PREFIXES);
- log_init();
+ log_init(1);
+#ifdef HAVE_LIBODBC
+ db_log_open();
+#endif
- if (use_chroot) {
- /*
-@@ -429,6 +432,9 @@ static int rsync_module(int f_in, int f_
- rprintf(FINFO,"rsync %s %s from %s@%s (%s)\n",
- am_sender?"on":"to",
+ #ifdef HAVE_PUTENV
+ if (*lp_prexfer_exec(i) || *lp_postxfer_exec(i)) {
+@@ -638,6 +641,9 @@ static int rsync_module(int f_in, int f_
+ rprintf(FLOG, "rsync %s %s from %s@%s (%s)\n",
+ am_sender ? "on" : "to",
request, auth_user, host, addr);
+#ifdef HAVE_LIBODBC
+ db_log_session();
+#endif
} else {
- rprintf(FINFO,"rsync %s %s from %s (%s)\n",
- am_sender?"on":"to",
---- configure.in 17 Apr 2004 18:40:16 -0000 1.191
-+++ configure.in 15 Apr 2004 18:51:14 -0000
-@@ -94,6 +94,8 @@ AC_ARG_WITH(rsync-path,
- [ --with-rsync-path=PATH set default --rsync-path to PATH (default: rsync)],
- [ RSYNC_PATH="$with_rsync_path" ],
- [ RSYNC_PATH="rsync" ])
-+AC_ARG_WITH(ODBC,
-+ [ --with-ODBC compile in support for ODBC database logging])
-
- AC_DEFINE_UNQUOTED(RSYNC_PATH, "$RSYNC_PATH", [location of rsync on remote machine])
-
-@@ -458,6 +460,14 @@ fi
- if test x"$with_included_popt" != x"yes"
- then
+ rprintf(FLOG, "rsync %s %s from %s (%s)\n",
+ am_sender ? "on" : "to",
+--- old/configure.in
++++ new/configure.in
+@@ -642,6 +642,12 @@ if test x"$with_included_popt" != x"yes"
AC_CHECK_LIB(popt, poptGetContext, , [with_included_popt=yes])
-+fi
-+
-+if test x"$with_ODBC" = x"yes"
-+then
-+ AC_CHECK_HEADERS(sql.h sqlext.h sqltypes.h)
-+ AC_CHECK_LIB(odbc,SQLExecDirect)
-+ EXTRA_OBJECT="$EXTRA_OBJECT dblog.o"
-+ AC_SUBST(EXTRA_OBJECT)
fi
++AC_ARG_ENABLE(ODBC, AC_HELP_STRING([--enable-ODBC], [compile in support for ODBC database logging]),
++ [ AC_CHECK_HEADERS(sql.h sqlext.h sqltypes.h)
++ AC_CHECK_LIB(odbc,SQLExecDirect)
++ EXTRA_OBJECT="$EXTRA_OBJECT dblog.o"
++ AC_SUBST(EXTRA_OBJECT) ])
++
AC_MSG_CHECKING([whether to use included libpopt])
---- /dev/null 1 Jan 1970 00:00:00 -0000
-+++ dblog.c 2004-04-07 23:18:56.000000000 -0700
-@@ -0,0 +1,352 @@
+ if test x"$with_included_popt" = x"yes"; then
+ AC_MSG_RESULT($srcdir/popt)
+--- old/db_log_error-list.txt
++++ new/db_log_error-list.txt
+@@ -0,0 +1,35 @@
++error type description
++0 not an error.
++1 authentication
++2 file/dir deletion failed
++3 connection closed
++4 read error
++5 multiplexing overflow
++6 unexpected tag
++7 over long v-string received
++8 invalid block length
++9 invalid checksum length
++10 invalid remainder length
++11 failed to write error
++12 attempting to send over-long vstring
++13 temporary filename too long
++14 lseek failed
++15 write failed
++16 rename failed
++17 rsync hack failed
++18 "invalid basis_dir index
++19 fstat failed
++20 is a directory
++21 open file failed
++22 mkstemp failed
++23 close failed
++24 failed verification
++25 IO error, skipping deletion.
++26 directory creation failed
++27 ignoring unsafe symbolic link
++28 symbolic link failed
++29 mknod failed
++30 failed to stat
++31 unlink
++32 failed to open file/directory
++33 open?
+--- old/dblog-tables-mysql.sql
++++ new/dblog-tables-mysql.sql
+@@ -0,0 +1,64 @@
++drop table transfer;
++drop table exit;
++drop table session;
++
++CREATE TABLE session (
++ id int auto_increment NOT NULL,
++ date timestamp NOT NULL,
++ ip_address varchar(15) NOT NULL,
++ username varchar(20) NOT NULL,
++ module_name varchar(20) NOT NULL,
++ module_path varchar(255) NOT NULL,
++ process_id int NOT NULL,
++ Primary Key (id)
++);
++
++CREATE TABLE transfer (
++ id int auto_increment NOT NULL,
++ session_id int NOT NULL,
++ date timestamp NOT NULL,
++ file_name varchar(255) NOT NULL,
++ file_size bigint NOT NULL,
++ bytes_transferred bigint NOT NULL,
++ checksum_bytes_transferred bigint NOT NULL,
++ operation varchar(20),
++ Primary Key (id),
++ foreign key (session_id) references session (id)
++);
++
++CREATE TABLE exit (
++ id int auto_increment NOT NULL,
++ session_id int NOT NULL,
++ date timestamp NOT NULL,
++ total_bytes_written bigint NOT NULL,
++ total_bytes_read bigint NOT NULL,
++ total_size bigint NOT NULL,
++ error_text varchar(128) NOT NULL,
++ error_code int NOT NULL,
++ error_file varchar(64) NOT NULL,
++ error_line int NOT NULL,
++ process_id int NOT NULL,
++ Primary Key (id),
++ foreign key (session_id) references session (id)
++);
++
++CREATE TABLE error (
++ id int auto_increment NOT NULL,
++ session_id int NOT NULL,
++ date timestamp NOT NULL,
++ logcode bigint NOT NULL,
++ error_number bigint NOT NULL,
++ error_text varchar(512),
++ PrimaryKey (id),
++ foreign key (session_id) references session (id)
++);
++
++CREATE TABLE delete (
++ id serial NOT NULL,
++ session_id int NOT NULL,
++ date timestamp NOT NULL,
++ path varchar(512) NOT NULL,
++ mode int NOT NULL,
++ PrimaryKey (id),
++ foreign key (session_id) references session (id)
++);
+--- old/dblog-tables-postgresql.sql
++++ new/dblog-tables-postgresql.sql
+@@ -0,0 +1,67 @@
++drop table transfer;
++drop table exit;
++drop table session;
++drop sequence session_id_seq;
++create sequence session_id_seq;
++
++CREATE TABLE "session" (
++ "id" int NOT NULL,
++ "date" timestamp NOT NULL default now(),
++ "ip_address" varchar(15) NOT NULL,
++ "username" varchar(20) NOT NULL,
++ "module_name" varchar(20) NOT NULL,
++ "module_path" varchar(255) NOT NULL,
++ "process_id" int NOT NULL,
++ Primary Key (id)
++);
++
++CREATE TABLE "transfer" (
++ "id" serial NOT NULL,
++ "session_id" int NOT NULL,
++ "date" timestamp NOT NULL default now(),
++ "file_name" varchar(512) NOT NULL,
++ "file_size" bigint NOT NULL,
++ "bytes_transferred" bigint NOT NULL,
++ "checksum_bytes_transferred" bigint NOT NULL,
++ "operation" varchar(20),
++ Primary Key (id),
++ foreign key (session_id) references session (id)
++);
++
++CREATE TABLE "exit" (
++ "id" serial NOT NULL,
++ "session_id" int NOT NULL,
++ "date" timestamp NOT NULL default now(),
++ "total_bytes_written" bigint NOT NULL,
++ "total_bytes_read" bigint NOT NULL,
++ "total_size" bigint NOT NULL,
++ "error_text" varchar(128) NOT NULL,
++ "error_code" int NOT NULL,
++ "error_file" varchar(64) NOT NULL,
++ "error_line" int NOT NULL,
++ "process_id" int NOT NULL,
++ Primary Key (id),
++ foreign key (session_id) references session (id)
++);
++
++CREATE TABLE "error" (
++ "id" serial NOT NULL,
++ "session_id" int NOT NULL,
++ "date" timestamp NOT NULL default now(),
++ "logcode" int NOT NULL,
++ "error_number" int NOT NULL,
++ "error_text" varchar(512),
++ Primary Key (id),
++ foreign key (session_id) references session (id)
++
++);
++
++CREATE TABLE "delete" (
++ "id" serial NOT NULL,
++ "session_id" int NOT NULL,
++ "date" timestamp NOT NULL default now(),
++ "path" varchar(512) NOT NULL,
++ "mode" int NOT NULL,
++ Primary Key (id),
++ foreign key (session_id) references session (id)
++);
+--- old/dblog.c
++++ new/dblog.c
+@@ -0,0 +1,549 @@
+/*
+ * ODBC Database logging functions
+ *
+
+SQLHENV db_environ_handle; /* Handle ODBC environment */
+long result; /* result of functions */
-+SQLHDBC db_handle= NULL; /* database connection handle */
-+SQLHSTMT sql_statement_handle; /* SQL statement handle */
++SQLHDBC db_handle_g = NULL; /* database connection handle for generator*/
++SQLHDBC db_handle_r = NULL; /* database connection handle for sender */
++SQLHSTMT sql_statement_handle_g; /* SQL statement handle for generator*/
++SQLHSTMT sql_statement_handle_r; /* SQL statement handle for receiver*/
++extern int am_daemon;
+extern int am_sender;
++extern int am_generator;
+extern char *auth_user;
+extern int module_id;
++extern int dry_run;
++
+
+char sql_status[10]; /* Status SQL */
+SQLINTEGER V_OD_err, V_OD_rowanz, V_OD_id;
+
+void db_log_open(void)
+{
-+ if (lp_database_logging(module_id)) {
-+ if (db_handle == NULL) {
-+ /* get ODBC environment handle */
-+ result = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&db_environ_handle);
-+ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
-+ rprintf(FERROR, "Error: couldn't get database environment handle\n");
-+ return;
-+ }
++ if (!lp_database_logging(module_id))
++ return;
+
-+ /* Setting database enviroment */
-+ result = SQLSetEnvAttr(db_environ_handle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
-+ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
-+ rprintf(FERROR, "Error: couldn't set database environment.\n");
-+ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
-+ db_environ_handle = NULL;
-+ return;
-+ }
++ /* get ODBC environment handle */
++ result = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&db_environ_handle);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ rprintf(FERROR, "Error: couldn't get database environment handle\n");
++ return;
++ }
+
-+ /* Get a database handle */
-+ result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle);
-+ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
-+ rprintf(FERROR, "Error: couldn't allocate database handle\n");
-+ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
-+ db_environ_handle = NULL;
-+ return;
-+ }
++ /* Setting database enviroment */
++ result = SQLSetEnvAttr(db_environ_handle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ rprintf(FERROR, "Error: couldn't set database environment.\n");
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
++ }
++ if (db_handle_g == NULL) {
++ /* Get a database handle for the generator*/
++ result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle_g);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ rprintf(FERROR, "Error: couldn't allocate database handle for generator\n");
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
++ }
+
-+ /* Set connection attributes */
-+ SQLSetConnectAttr(db_handle, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
++ /* Set connection attributes for the generator db connection */
++ SQLSetConnectAttr(db_handle_g, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
+
-+ /* Connect to the database. */
-+ result = SQLConnect(db_handle, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS,
-+ (SQLCHAR*) lp_database_username(module_id), SQL_NTS,
-+ (SQLCHAR*) lp_database_password(module_id), SQL_NTS);
++ /* get the database connection for the generator. */
++ result = SQLConnect(db_handle_g, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS,
++ (SQLCHAR*) lp_database_username(module_id), SQL_NTS,
++ (SQLCHAR*) lp_database_password(module_id), SQL_NTS);
+
-+ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
-+ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1,
-+ sql_status, &V_OD_err,V_OD_msg,100,&V_OD_mlen);
-+ rprintf(FERROR,"Error Connecting to Database %s\n",V_OD_msg);
-+ SQLFreeHandle(SQL_HANDLE_DBC,db_handle);
-+ db_handle = NULL;
-+ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
-+ db_environ_handle = NULL;
-+ return;
-+ }
-+ rprintf(FLOG,"Connected to database!\n");
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_g, 1,
++ sql_status, &V_OD_err, V_OD_msg, 100, &V_OD_mlen);
++ rprintf(FERROR,"Error Connecting to Database (generator) %s\n",V_OD_msg);
++ SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g);
++ db_handle_g = NULL;
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
++ }
++ rprintf(FLOG,"Connected to database for generator!\n");
++ } else {
++ rprintf(FERROR,"Already connected to database for generator\n");
++ }
++ if (db_handle_r == NULL) {
++ /* Get a database handle for the receiver */
++ result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle_r);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ rprintf(FERROR, "Error: couldn't allocate database handle for receiver\n");
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
++ }
+
-+ /* get SQL statement handle */
-+ result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle, &sql_statement_handle);
-+ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
-+ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
-+ rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg);
-+ SQLDisconnect(db_handle);
-+ SQLFreeHandle(SQL_HANDLE_DBC,db_handle);
-+ db_handle = NULL;
-+ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
-+ db_environ_handle = NULL;
-+ return;
-+ }
-+ } else {
-+ rprintf(FERROR,"Already connected to database\n");
++ /* Set connection attributes for the receiver db connection */
++ SQLSetConnectAttr(db_handle_r, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
++
++ /* get the generator connection for the receiver. */
++ result = SQLConnect(db_handle_r, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS,
++ (SQLCHAR*) lp_database_username(module_id), SQL_NTS,
++ (SQLCHAR*) lp_database_password(module_id), SQL_NTS);
++
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_r,1,
++ sql_status, &V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error Connecting to Database (receiver) %s\n",V_OD_msg);
++ SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r);
++ db_handle_r = NULL;
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
+ }
++ rprintf(FLOG,"Connected to database for receiver!\n");
++ } else {
++ rprintf(FERROR,"Already connected to database for receiver\n");
++ }
++
++ /* get SQL statement handle for generator */
++ result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle_g, &sql_statement_handle_g);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_g,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg);
++ SQLDisconnect(db_handle_g);
++ SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g);
++ db_handle_g = NULL;
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
++ }
++
++ /* get SQL statement handle for receiver */
++ result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle_r, &sql_statement_handle_r);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_r,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg);
++ SQLDisconnect(db_handle_r);
++ SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r);
++ db_handle_r = NULL;
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
+ }
+}
+
+void db_log_close()
+{
-+ if (lp_database_logging(module_id)) {
-+ if (sql_statement_handle != NULL) {
++ if (!lp_database_logging(module_id))
++ return;
++
++ if (am_generator) {
++ if (sql_statement_handle_g != NULL) {
+ /* free the statement handle first */
-+ SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle);
-+ sql_statement_handle = NULL;
++ SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle_g);
++ sql_statement_handle_g = NULL;
+ } else {
-+ rprintf(FERROR,"No sql statement handle to close\n");
++ rprintf(FERROR,"No generator sql statement handle to close\n");
+ }
-+ if (db_handle != NULL) {
++
++ if (db_handle_g != NULL) {
+ /* disconnect, and free the database handle. */
-+ SQLDisconnect(db_handle);
-+ SQLFreeHandle(SQL_HANDLE_DBC,db_handle);
-+ db_handle = NULL;
++ SQLDisconnect(db_handle_g);
++ SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g);
++ db_handle_g = NULL;
+ } else {
-+ rprintf(FERROR,"Database already closed");
++ rprintf(FERROR,"Generator database connection already closed\n");
+ }
-+ if (db_environ_handle != NULL) {
-+ /* free the environment handle */
-+ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
-+ db_environ_handle = NULL;
++ } else { /* must be receiver */
++ if (sql_statement_handle_r != NULL) {
++ /* free the statement handle first */
++ SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle_r);
++ sql_statement_handle_r = NULL;
+ } else {
-+ rprintf(FERROR,"No environment handle to close");
++ rprintf(FERROR,"No receiver sql statement handle to close\n");
++ }
++
++ if (db_handle_r != NULL) {
++ /* disconnect, and free the database handle. */
++ SQLDisconnect(db_handle_r);
++ SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r);
++ db_handle_r = NULL;
++ } else {
++ rprintf(FERROR,"Receiver database connection already closed\n");
+ }
+ }
++
++ if (db_environ_handle != NULL) {
++ /* free the environment handle */
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ } else {
++ rprintf(FERROR,"No environment handle to close\n");
++ }
+}
+
+static long get_unique_session_id()
+{
+ long unique;
+ char strSqlStatement[1024];
++ SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
++ SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
+
+ if (db_handle != NULL) {
+ /* choose the appropriate select statement based upon which DBMS we're using.
+ * object (sequence), others use an auto increment datatype and have a method
+ * to get the last ID inserted using this connection. */
+ if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0) {
-+ snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT NEXTVAL('%s');",lp_sequence_name(module_id));
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "SELECT NEXTVAL('%s');", lp_sequence_name(module_id));
+ } else if (strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0) {
-+ snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT %s.NEXTVAL FROM dual;",lp_sequence_name(module_id));
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "SELECT %s.NEXTVAL FROM dual;", lp_sequence_name(module_id));
+ } else if (strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0) {
-+ snprintf(strSqlStatement,sizeof(strSqlStatement),"VALUES NEXTVAL FOR %s;",lp_sequence_name(module_id));
-+ } else if (strcmp(lp_unique_id_method(module_id),"last_insert_id") == 0) { /* MySql */
-+ snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT LAST_INSERT_ID()");
-+ } else if (strcmp(lp_unique_id_method(module_id),"@@IDENTITY") == 0) { /* Sybase */
-+ snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT @@IDENTITY");
-+ } else if (strcmp(lp_unique_id_method(module_id),"custom") == 0){ /* Users custom statement */
-+ snprintf(strSqlStatement,sizeof(strSqlStatement),lp_custom_unique_id_select(module_id));
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "VALUES NEXTVAL FOR %s;",lp_sequence_name(module_id));
++ } else if (strcmp(lp_unique_id_method(module_id),"last_insert_id") == 0) { /* MySql */
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "SELECT LAST_INSERT_ID()");
++ } else if (strcmp(lp_unique_id_method(module_id),"@@IDENTITY") == 0) { /* Sybase */
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "SELECT @@IDENTITY");
++ } else if (strcmp(lp_unique_id_method(module_id),"custom") == 0){ /* Users custom statement */
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ lp_custom_unique_id_select(module_id));
+ }
+
+ /* bind the 1st column to unique */
+ return -1;
+}
+
-+
+void db_log_session()
+{
+ char strSqlStatement[1024];
+ int gotSessionID = 0;
-+ if (lp_database_logging(module_id)) {
-+ /* if we're using a sequence via the nextval command to get a unique ID, we need to get it before
-+ * we do the insert. We also get the unique ID now if custom, and get_custom_id_before_insert is set. */
++ SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
++ SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
++
++ if (!lp_database_logging(module_id))
++ return;
++
++ if (db_handle != NULL) {
++ /* if we're using a sequence via the nextval command to
++ * get a unique ID, we need to get it before we do the
++ * insert. We also get the unique ID now if custom,
++ * and get_custom_id_before_insert is set. */
+ if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0
-+ || strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0
-+ || strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0
-+ || (strcmp(lp_unique_id_method(module_id),"custom") == 0
-+ && lp_get_custom_id_before_insert(module_id))) {
++ || strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0
++ || strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0
++ || (strcmp(lp_unique_id_method(module_id),"custom") == 0
++ && lp_get_custom_id_before_insert(module_id))) {
+ session_id = get_unique_session_id();
+ gotSessionID = 1;
-+ snprintf(strSqlStatement,sizeof(strSqlStatement),"INSERT INTO %s (id, date, ip_address, username, module_name, module_path, process_id) VALUES ('%ld', '%s', '%s', '%s','%s','%s','%d');",lp_session_table_name(module_id),session_id,timestring(time(NULL)),client_addr(0),auth_user,lp_name(module_id),lp_path(module_id),getpid());
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "INSERT INTO %s (id, date, ip_address, username, module_name, module_path, process_id) VALUES ('%ld', '%s', '%s', '%s','%s','%s','%d');",
++ lp_session_table_name(module_id), session_id, timestring(time(NULL)), client_addr(0),
++ auth_user, lp_name(module_id), lp_path(module_id), getpid());
+ } else {
+ /* Otherwise the ID gets created automatically, and we get the ID it used after the insert. */
-+ snprintf(strSqlStatement,sizeof(strSqlStatement),"INSERT INTO %s (date, ip_address, username, module_name, module_path, process_id) VALUES ('%s', '%s', '%s', '%s','%s','%d');",lp_session_table_name(module_id),timestring(time(NULL)),client_addr(0),auth_user,lp_name(module_id),lp_path(module_id),getpid());
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "INSERT INTO %s (date, ip_address, username, module_name, module_path, process_id) VALUES ('%s', '%s', '%s', '%s','%s','%d');",
++ lp_session_table_name(module_id), timestring(time(NULL)), client_addr(0), auth_user,
++ lp_name(module_id), lp_path(module_id), getpid());
+ }
+
+ /* Insert the new session into the database */
+ if (gotSessionID == 0) {
+ session_id = get_unique_session_id();
+ }
-+ }
-+ else {
++ } else {
+ rprintf(FERROR,"Error at db_log_session: Not connected to database!\n");
+ }
+}
+void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation)
+{
+ extern struct stats stats;
-+ char strSqlStatement[1024];
-+ char strFilePath[255];
-+ char strFileName[255];
++ char strSqlStatement[2048];
++ char strFileName[MAXPATHLEN];
++ char *strFileNamePtr;
+ char strFileSize[255];
+ int64 intBytesTransferred;
+ int64 intCheckSumBytes;
++ SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
++ SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
+
-+ if (lp_database_logging(module_id)) {
-+ if (db_handle != NULL) {
-+ snprintf(strFileName,sizeof(strFileName), "%s",f_name(file));
-+ snprintf(strFilePath, sizeof(strFilePath), "%s", file->basedir?file->basedir:"");
-+ snprintf(strFileSize,sizeof(strFileSize),"%.0f", (double)file->length);
-+ if (am_sender) {
-+ intBytesTransferred = stats.total_written - initial_stats->total_written;
-+ } else {
-+ intBytesTransferred = stats.total_read - initial_stats->total_read;
-+ }
++ if (!lp_database_logging(module_id))
++ return;
+
-+ if (!am_sender) {
-+ intCheckSumBytes = stats.total_written - initial_stats->total_written;
-+ } else {
-+ intCheckSumBytes = stats.total_read - initial_stats->total_read;
-+ }
++ if (db_handle != NULL) {
++ strFileNamePtr = f_name(file, NULL);
++ if (am_sender && file->dir.root) {
++ pathjoin(strFileName, sizeof strFileName,
++ file->dir.root, strFileNamePtr);
++ strFileNamePtr = strFileName;
++ }
++ clean_fname(strFileNamePtr, 0);
++ if (*strFileNamePtr == '/')
++ strFileNamePtr++;
+
-+ snprintf(strSqlStatement,sizeof(strSqlStatement),"INSERT INTO %s (session_id,date,file_path, file_name, file_size, bytes_transferred, checksum_bytes_transferred, operation) VALUES ('%ld','%s','%s','%s','%s','%Ld','%Ld','%s');",lp_transfer_table_name(module_id),session_id,timestring(time(NULL)),sanitizeSql(strFilePath),sanitizeSql(strFileName),strFileSize,intBytesTransferred,intCheckSumBytes,operation);
-+ result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
-+ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
-+ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
-+ rprintf(FERROR,"Error at db_log_transfer: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
-+ if (result == SQL_INVALID_HANDLE)
-+ rprintf(FERROR,"INVALID HANDLE\n");
-+ }
++ snprintf(strFileSize, sizeof strFileSize, "%.0f", (double)F_LENGTH(file));
++ if (am_sender) {
++ intBytesTransferred = stats.total_written - initial_stats->total_written;
+ } else {
-+ rprintf(FERROR,"Error at db_log_transfer: Not connected to database!\n");
++ intBytesTransferred = stats.total_read - initial_stats->total_read;
+ }
++
++ if (!am_sender) {
++ intCheckSumBytes = stats.total_written - initial_stats->total_written;
++ } else {
++ intCheckSumBytes = stats.total_read - initial_stats->total_read;
++ }
++
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "INSERT INTO %s (session_id,date, file_name, file_size, bytes_transferred, checksum_bytes_transferred, operation) VALUES ('%ld','%s','%s','%s','%Ld','%Ld','%s');",
++ lp_transfer_table_name(module_id), session_id, timestring(time(NULL)),
++ sanitizeSql(strFileNamePtr), strFileSize, intBytesTransferred,
++ intCheckSumBytes, operation);
++ result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error at db_log_transfer: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
++ if (result == SQL_INVALID_HANDLE)
++ rprintf(FERROR,"INVALID HANDLE\n");
++ }
++ } else {
++ rprintf(FERROR,"Error at db_log_transfer: Not connected to database!\n");
+ }
+}
+
-+
+void db_log_exit(int code, const char *file, int line)
+{
+ char strSqlStatement[2048];
+ const char *error_text;
+ extern struct stats stats;
++ SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
++ SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
++
++ if (!lp_database_logging(module_id))
++ return;
++
+ if (db_handle != NULL) {
-+ if (lp_database_logging(module_id)) {
-+ if (code != 0) {
-+ error_text = rerr_name(code);
-+ if (!error_text) {
-+ error_text = "unexplained error";
-+ }
-+ } else {
-+ error_text = "";
++ if (code != 0) {
++ error_text = rerr_name(code);
++ if (!error_text) {
++ error_text = "unexplained error";
+ }
-+ snprintf(strSqlStatement,sizeof(strSqlStatement),"INSERT INTO %s (session_id, date, total_bytes_written,total_bytes_read,total_size,error_text,error_code,error_file,error_line) VALUES ('%ld','%s','%Ld','%Ld','%Ld','%s','%d','%s','%d');",lp_exit_table_name(module_id),session_id,timestring(time(NULL)),stats.total_written,stats.total_read,stats.total_size,error_text,code,file,line);
++ } else {
++ error_text = "";
++ }
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "INSERT INTO %s (session_id, date, total_bytes_written,total_bytes_read,total_size,error_text,error_code,error_file,error_line,process_id) VALUES ('%ld','%s','%Ld','%Ld','%Ld','%s','%d','%s','%d','%d');",
++ lp_exit_table_name(module_id), session_id, timestring(time(NULL)), stats.total_written,
++ stats.total_read, stats.total_size, error_text, code, file, line, getpid());
+
-+ result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
++ result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
+
-+ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
-+ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
-+ rprintf(FERROR,"Error at db_log_exit: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
-+ }
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error at db_log_exit: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
+ }
+ } else {
+ rprintf(FERROR,"Error at db_log_exit: Not connected to database!\n");
+ }
+}
---- /dev/null 1 Jan 1970 00:00:00 -0000
-+++ dblog-tables-mysql.sql 2004-04-07 23:00:13.000000000 -0700
-@@ -0,0 +1,43 @@
-+drop table transfer;
-+drop table exit;
-+drop table session;
+
-+CREATE TABLE session (
-+ id int auto_increment NOT NULL,
-+ date timestamp NOT NULL,
-+ ip_address varchar(15) NOT NULL,
-+ username varchar(20) NOT NULL,
-+ module_name varchar(20) NOT NULL,
-+ module_path varchar(255) NOT NULL,
-+ process_id int NOT NULL,
-+ Primary Key (id)
-+);
++void db_log_delete(char *fname, int mode)
++{
++ char strSqlStatement[2048];
++ SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
++ SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
+
-+CREATE TABLE transfer (
-+ id int auto_increment NOT NULL,
-+ session_id int NOT NULL,
-+ date timestamp NOT NULL,
-+ file_path varchar(255) NOT NULL,
-+ file_name varchar(255) NOT NULL,
-+ file_size bigint NOT NULL,
-+ bytes_transferred bigint NOT NULL,
-+ checksum_bytes_transferred bigint NOT NULL,
-+ operation varchar(20),
-+ Primary Key (id),
-+ foreign key (session_id) references session (id)
-+);
++ if (!am_daemon || dry_run || !lp_database_logging(module_id))
++ return;
+
-+CREATE TABLE exit (
-+ id int auto_increment NOT NULL,
-+ session_id int NOT NULL,
-+ date timestamp NOT NULL,
-+ total_bytes_written bigint NOT NULL,
-+ total_bytes_read bigint NOT NULL,
-+ total_size bigint NOT NULL,
-+ error_text varchar(128) NOT NULL,
-+ error_code int NOT NULL,
-+ error_file varchar(64) NOT NULL,
-+ error_line int NOT NULL,
-+ Primary Key (id),
-+ foreign key (session_id) references session (id)
-+);
---- /dev/null 1 Jan 1970 00:00:00 -0000
-+++ dblog-tables-postgresql.sql 2004-04-07 22:59:55.000000000 -0700
-@@ -0,0 +1,45 @@
-+drop table transfer;
-+drop table exit;
-+drop table session;
-+drop sequence session_id_seq;
-+create sequence session_id_seq;
++ if (db_handle != NULL) {
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "INSERT INTO %s (session_id, date, path, mode) VALUES ('%ld','%s','%s','%d');",
++ lp_delete_table_name(module_id), session_id, timestring(time(NULL)), sanitizeSql(fname), mode);
+
-+CREATE TABLE "session" (
-+ "id" int NOT NULL,
-+ "date" timestamp NOT NULL default now(),
-+ "ip_address" varchar(15) NOT NULL,
-+ "username" varchar(20) NOT NULL,
-+ "module_name" varchar(20) NOT NULL,
-+ "module_path" varchar(255) NOT NULL,
-+ "process_id" int NOT NULL,
-+ Primary Key (id)
-+);
++ result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
+
-+CREATE TABLE "transfer" (
-+ "id" serial NOT NULL,
-+ "session_id" int NOT NULL,
-+ "date" timestamp NOT NULL default now(),
-+ "file_path" varchar(512) NOT NULL,
-+ "file_name" varchar(512) NOT NULL,
-+ "file_size" bigint NOT NULL,
-+ "bytes_transferred" bigint NOT NULL,
-+ "checksum_bytes_transferred" bigint NOT NULL,
-+ "operation" varchar(20),
-+ Primary Key (id),
-+ foreign key (session_id) references session (id)
-+);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error at db_log_delete: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
++ }
++ } else {
++ rprintf(FERROR,"Error at db_log_delete: Not connected to database!\n");
++ }
++}
+
-+CREATE TABLE "exit" (
-+ "id" serial NOT NULL,
-+ "session_id" int NOT NULL,
-+ "date" timestamp NOT NULL default now(),
-+ "total_bytes_written" bigint NOT NULL,
-+ "total_bytes_read" bigint NOT NULL,
-+ "total_size" bigint NOT NULL,
-+ "error_text" varchar(128) NOT NULL,
-+ "error_code" int NOT NULL,
-+ "error_file" varchar(64) NOT NULL,
-+ "error_line" int NOT NULL,
-+ Primary Key (id),
-+ foreign key (session_id) references session (id)
-+);
---- loadparm.c 4 Feb 2004 07:31:29 -0000 1.50
-+++ loadparm.c 8 Apr 2004 06:31:18 -0000
-@@ -122,6 +122,17 @@ typedef struct
- BOOL list;
- BOOL use_chroot;
- BOOL transfer_logging;
-+ BOOL database_logging;
++void db_log_error(enum logcode code, int errcode, const char *format,...)
++{
++ char strSqlStatement[MAXPATHLEN+1024];
++ va_list ap;
++ char buf[MAXPATHLEN+512];
++ size_t len;
++ SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
++ SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
++
++ if (!lp_database_logging(module_id))
++ return;
++
++ va_start(ap, format);
++ len = vsnprintf(buf, sizeof buf, format, ap);
++ va_end(ap);
++
++ /* Deal with buffer overruns. Instead of panicking, just
++ * truncate the resulting string. (Note that configure ensures
++ * that we have a vsnprintf() that doesn't ever return -1.) */
++ if (len > sizeof buf - 1) {
++ const char ellipsis[] = "[...]";
++
++ /* Reset length, and zero-terminate the end of our buffer */
++ len = sizeof buf - 1;
++ buf[len] = '\0';
++
++ /* Copy the ellipsis to the end of the string, but give
++ * us one extra character:
++ *
++ * v--- null byte at buf[sizeof buf - 1]
++ * abcdefghij0
++ * -> abcd[...]00 <-- now two null bytes at end
++ *
++ * If the input format string has a trailing newline,
++ * we copy it into that extra null; if it doesn't, well,
++ * all we lose is one byte. */
++ strncpy(buf+len-sizeof ellipsis, ellipsis, sizeof ellipsis);
++ if (format[strlen(format)-1] == '\n') {
++ buf[len-1] = '\n';
++ }
++ }
++
++ if (db_handle != NULL) {
++ snprintf(strSqlStatement, sizeof strSqlStatement,
++ "INSERT INTO %s (session_id, date, logcode, error_number, error_text) VALUES ('%ld','%s','%d','%d','%s');",
++ lp_error_table_name(module_id), session_id, timestring(time(NULL)), code, errcode, sanitizeSql(buf));
++
++ result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
++
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error at db_log_error: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
++ }
++ } else {
++ rprintf(FERROR,"Error at db_log_error: Not connected to database!\n");
++ }
++}
+--- old/instructions
++++ new/instructions
+@@ -0,0 +1,84 @@
++This patch adds the following options:
++
++"database logging"
++ If set to True, rsync will attempt to connect to
++ the specified datasource and write to the named tables.
++ Defaults to False.
++
++"database datasource"
++ Specifies the name of the ODBC data source to use.
++
++"database username"
++ The username to use when connecting to the database.
++
++"database password"
++ The password to use when connecting to the database.
++
++"transfer table name"
++ The name of the transfer table to log to. This table contains individual
++ filenames, file sizes, bytes transferred, checksum bytes transferred,
++ operation (send or receive), and a timestamp.
++
++"session table name"
++ The name of the session table to log to. This table contains the username,
++ module name, module path, ip address, process ID, and a timestamp.
++
++"exit table name"
++
++ The name of the exit table to log to. This table contains the total bytes
++ read, total bytes written, total size of all files, error code, line the
++ error occured at, file the error occured at and the text of the error.
++ (most of which will be blank if the program exited normally).
++
++"delete table name"
++
++ The name of the table to log deleted files/directories to.
++
++"error table name"
++
++ The name of the table errors will be logged to.
++
++"unique id method"
++ Different databases use different methods to get a unique identifier.
++ Some databases support sequence objects, and use various forms of the
++ nextval command to retrieve a unique identifier from it. Other databases
++ support an autonumber field, and support different methds of retrieving
++ the ID used in the last insert. Valid values for this option are:
++
++ nextval-postgres
++ uses the syntax of nextval for PostgreSQL databases
++
++ nextval-oracle
++ uses the syntax of nextval for Oracle databases
++
++ nextval-db2
++ uses the syntax of nextval for DB2 databases
++
++ last_insert_id
++ uses the last_insert_id() command for the MySQL databases
++
++ @@IDENTITY
++ uses the @@IDENTITY command for Sybase databases
++
++ custom
++ Define your own method to get a unique identifier. See the
++ "custom unique id select", and the "get custom id before select"
++ parameters.
++
++"sequence name"
++ If your database supports sequences, list the name of the sequence to use
++ for the session unique identifier.
++
++"custom unique id select"
++ Only used if you specify the custom method in "unique id method". This is
++ a SQL statement to be executed to get a unique ID. This SQL statement must
++ return one column with the unique ID to use for the session ID. Should be
++ used in concert with the "get custom id before select" parameter.
++
++"get custom id before insert"
++ This parameter is ignored unless the "unique id method" is set to custom.
++ If set to true, the "custom unique id select" statement will be executed
++ BEFORE the session row is inserted into the database. (as is done when a
++ sequence is used for unique IDs). If False the statement will be executed
++ after the session row is inserted (as is done when the session ID is
++ automatically generates unique IDs). Defaults to True.
+--- old/loadparm.c
++++ new/loadparm.c
+@@ -121,9 +121,16 @@ typedef struct
+ {
+ char *auth_users;
+ char *comment;
++ char *custom_unique_id_select;
+ char *database_datasource;
-+ char *database_username;
+ char *database_password;
-+ char *transfer_table_name;
++ char *database_username;
++ char *delete_table_name;
+ char *dont_compress;
++ char *error_table_name;
+ char *exclude;
+ char *exclude_from;
+ char *exit_table_name;
-+ char *session_table_name;
+ char *filter;
+ char *gid;
+ char *hosts_allow;
+@@ -141,14 +148,20 @@ typedef struct
+ char *prexfer_exec;
+ char *refuse_options;
+ char *secrets_file;
+ char *sequence_name;
++ char *session_table_name;
+ char *temp_dir;
++ char *transfer_table_name;
+ char *uid;
+ char *unique_id_method;
-+ char *custom_unique_id_select;
+
+ int max_connections;
+ int max_verbosity;
+ int syslog_facility;
+ int timeout;
+
++ BOOL database_logging;
+ BOOL get_custom_id_before_insert;
BOOL ignore_errors;
- char *uid;
- char *gid;
-@@ -154,6 +165,17 @@ static service sDefault =
- True, /* list */
- True, /* use chroot */
- False, /* transfer logging */
-+ False, /* Database Logging */
-+ NULL, /* Database datasource */
-+ NULL, /* Database username */
-+ NULL, /* Database password */
-+ NULL, /* Transfer table name */
-+ NULL, /* Exit table name */
-+ NULL, /* Session table name */
-+ NULL, /* sequence name */
-+ NULL, /* unique method */
-+ NULL, /* custom unique id select*/
-+ True, /* get custom id before insert */
- False, /* ignore errors */
- "nobody",/* uid */
+ BOOL ignore_nonreadable;
+ BOOL list;
+@@ -168,9 +181,16 @@ static service sDefault =
+ {
+ /* auth_users; */ NULL,
+ /* comment; */ NULL,
++ /* custom_unique_id_select; */ NULL,
++ /* database_datasource; */ NULL,
++ /* database_password; */ NULL,
++ /* database_username; */ NULL,
++ /* delete_table_name; */ NULL,
+ /* dont_compress; */ "*.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz",
++ /* error_table_name; */ NULL,
+ /* exclude; */ NULL,
+ /* exclude_from; */ NULL,
++ /* exit_table_name; */ NULL,
+ /* filter; */ NULL,
+ /* gid; */ NOBODY_GROUP,
+ /* hosts_allow; */ NULL,
+@@ -188,14 +208,20 @@ static service sDefault =
+ /* prexfer_exec; */ NULL,
+ /* refuse_options; */ NULL,
+ /* secrets_file; */ NULL,
++ /* sequence_name; */ NULL,
++ /* session_table_name; */ NULL,
+ /* temp_dir; */ NULL,
++ /* transfer_table_name; */ NULL,
+ /* uid; */ NOBODY_USER,
++ /* unique_id_method; */ NULL,
-@@ -292,6 +314,17 @@ static struct parm_struct parm_table[] =
- {"include", P_STRING, P_LOCAL, &sDefault.include, NULL, 0},
- {"include from", P_STRING, P_LOCAL, &sDefault.include_from,NULL, 0},
- {"transfer logging", P_BOOL, P_LOCAL, &sDefault.transfer_logging,NULL,0},
-+ {"database logging", P_BOOL, P_LOCAL, &sDefault.database_logging,NULL,0},
-+ {"database datasource",P_STRING,P_LOCAL, &sDefault.database_datasource,NULL,0},
-+ {"database username",P_STRING, P_LOCAL, &sDefault.database_username,NULL,0},
-+ {"database password",P_STRING, P_LOCAL, &sDefault.database_password,NULL,0},
-+ {"transfer table name",P_STRING,P_LOCAL, &sDefault.transfer_table_name,NULL,0},
-+ {"exit table name", P_STRING, P_LOCAL, &sDefault.exit_table_name,NULL,0},
-+ {"session table name",P_STRING, P_LOCAL, &sDefault.session_table_name,NULL,0},
-+ {"sequence name", P_STRING, P_LOCAL, &sDefault.sequence_name,NULL,0},
-+ {"unique id method", P_STRING, P_LOCAL, &sDefault.unique_id_method,NULL,0},
-+ {"custom unique id select",P_STRING,P_LOCAL,&sDefault.custom_unique_id_select,NULL,0},
-+ {"get custom id before insert",P_BOOL,P_LOCAL,&sDefault.get_custom_id_before_insert,NULL,0},
- {"ignore errors", P_BOOL, P_LOCAL, &sDefault.ignore_errors,NULL,0},
- {"log format", P_STRING, P_LOCAL, &sDefault.log_format, NULL, 0},
- {"refuse options", P_STRING, P_LOCAL, &sDefault.refuse_options,NULL, 0},
-@@ -359,6 +392,17 @@ FN_LOCAL_BOOL(lp_read_only, read_only)
- FN_LOCAL_BOOL(lp_list, list)
- FN_LOCAL_BOOL(lp_use_chroot, use_chroot)
- FN_LOCAL_BOOL(lp_transfer_logging, transfer_logging)
-+FN_LOCAL_BOOL(lp_database_logging, database_logging)
+ /* max_connections; */ 0,
+ /* max_verbosity; */ 1,
+ /* syslog_facility; */ LOG_DAEMON,
+ /* timeout; */ 0,
+
++ /* database_logging; */ False,
++ /* get_custom_id_before_insert; */ True,
+ /* ignore_errors; */ False,
+ /* ignore_nonreadable; */ False,
+ /* list; */ True,
+@@ -294,10 +320,19 @@ static struct parm_struct parm_table[] =
+
+ {"auth users", P_STRING, P_LOCAL, &sDefault.auth_users, NULL,0},
+ {"comment", P_STRING, P_LOCAL, &sDefault.comment, NULL,0},
++ {"custom unique id select",P_STRING,P_LOCAL,&sDefault.custom_unique_id_select,NULL,0},
++ {"database datasource",P_STRING,P_LOCAL, &sDefault.database_datasource,NULL,0},
++ {"database logging", P_BOOL, P_LOCAL, &sDefault.database_logging, NULL,0},
++ {"database password", P_STRING, P_LOCAL, &sDefault.database_password, NULL,0},
++ {"database username", P_STRING, P_LOCAL, &sDefault.database_username, NULL,0},
++ {"delete table name", P_STRING, P_LOCAL, &sDefault.delete_table_name, NULL,0},
+ {"dont compress", P_STRING, P_LOCAL, &sDefault.dont_compress, NULL,0},
++ {"error table name", P_STRING, P_LOCAL, &sDefault.error_table_name, NULL,0},
+ {"exclude from", P_STRING, P_LOCAL, &sDefault.exclude_from, NULL,0},
+ {"exclude", P_STRING, P_LOCAL, &sDefault.exclude, NULL,0},
++ {"exit table name", P_STRING, P_LOCAL, &sDefault.exit_table_name, NULL,0},
+ {"filter", P_STRING, P_LOCAL, &sDefault.filter, NULL,0},
++ {"get custom id before insert",P_BOOL,P_LOCAL,&sDefault.get_custom_id_before_insert,NULL,0},
+ {"gid", P_STRING, P_LOCAL, &sDefault.gid, NULL,0},
+ {"hosts allow", P_STRING, P_LOCAL, &sDefault.hosts_allow, NULL,0},
+ {"hosts deny", P_STRING, P_LOCAL, &sDefault.hosts_deny, NULL,0},
+@@ -322,12 +357,16 @@ static struct parm_struct parm_table[] =
+ {"read only", P_BOOL, P_LOCAL, &sDefault.read_only, NULL,0},
+ {"refuse options", P_STRING, P_LOCAL, &sDefault.refuse_options, NULL,0},
+ {"secrets file", P_STRING, P_LOCAL, &sDefault.secrets_file, NULL,0},
++ {"sequence name", P_STRING, P_LOCAL, &sDefault.sequence_name, NULL,0},
++ {"session table name",P_STRING, P_LOCAL, &sDefault.session_table_name,NULL,0},
+ {"strict modes", P_BOOL, P_LOCAL, &sDefault.strict_modes, NULL,0},
+ {"syslog facility", P_ENUM, P_LOCAL, &sDefault.syslog_facility,enum_facilities,0},
+ {"temp dir", P_PATH, P_LOCAL, &sDefault.temp_dir, NULL,0},
+ {"timeout", P_INTEGER,P_LOCAL, &sDefault.timeout, NULL,0},
+ {"transfer logging", P_BOOL, P_LOCAL, &sDefault.transfer_logging, NULL,0},
++ {"transfer table name",P_STRING,P_LOCAL, &sDefault.transfer_table_name,NULL,0},
+ {"uid", P_STRING, P_LOCAL, &sDefault.uid, NULL,0},
++ {"unique id method", P_STRING, P_LOCAL, &sDefault.unique_id_method, NULL,0},
+ {"use chroot", P_BOOL, P_LOCAL, &sDefault.use_chroot, NULL,0},
+ {"write only", P_BOOL, P_LOCAL, &sDefault.write_only, NULL,0},
+ {NULL, P_BOOL, P_NONE, NULL, NULL,0}
+@@ -383,9 +422,16 @@ FN_GLOBAL_INTEGER(lp_rsync_port, &Global
+
+ FN_LOCAL_STRING(lp_auth_users, auth_users)
+ FN_LOCAL_STRING(lp_comment, comment)
++FN_LOCAL_STRING(lp_custom_unique_id_select,custom_unique_id_select)
+FN_LOCAL_STRING(lp_database_datasource, database_datasource)
-+FN_LOCAL_STRING(lp_database_username, database_username)
+FN_LOCAL_STRING(lp_database_password, database_password)
-+FN_LOCAL_STRING(lp_transfer_table_name, transfer_table_name)
++FN_LOCAL_STRING(lp_database_username, database_username)
++FN_LOCAL_STRING(lp_delete_table_name,delete_table_name)
+ FN_LOCAL_STRING(lp_dont_compress, dont_compress)
++FN_LOCAL_STRING(lp_error_table_name,error_table_name)
+ FN_LOCAL_STRING(lp_exclude, exclude)
+ FN_LOCAL_STRING(lp_exclude_from, exclude_from)
+FN_LOCAL_STRING(lp_exit_table_name, exit_table_name)
-+FN_LOCAL_STRING(lp_session_table_name,session_table_name)
+ FN_LOCAL_STRING(lp_filter, filter)
+ FN_LOCAL_STRING(lp_gid, gid)
+ FN_LOCAL_STRING(lp_hosts_allow, hosts_allow)
+@@ -403,14 +449,20 @@ FN_LOCAL_STRING(lp_postxfer_exec, postxf
+ FN_LOCAL_STRING(lp_prexfer_exec, prexfer_exec)
+ FN_LOCAL_STRING(lp_refuse_options, refuse_options)
+ FN_LOCAL_STRING(lp_secrets_file, secrets_file)
+FN_LOCAL_STRING(lp_sequence_name,sequence_name)
++FN_LOCAL_STRING(lp_session_table_name,session_table_name)
+ FN_LOCAL_INTEGER(lp_syslog_facility, syslog_facility)
+ FN_LOCAL_STRING(lp_temp_dir, temp_dir)
++FN_LOCAL_STRING(lp_transfer_table_name, transfer_table_name)
+ FN_LOCAL_STRING(lp_uid, uid)
+FN_LOCAL_STRING(lp_unique_id_method,unique_id_method)
-+FN_LOCAL_STRING(lp_custom_unique_id_select,custom_unique_id_select)
+
+ FN_LOCAL_INTEGER(lp_max_connections, max_connections)
+ FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity)
+ FN_LOCAL_INTEGER(lp_timeout, timeout)
+
++FN_LOCAL_BOOL(lp_database_logging, database_logging)
+FN_LOCAL_BOOL(lp_get_custom_id_before_insert,get_custom_id_before_insert)
FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
- FN_LOCAL_STRING(lp_uid, uid)
---- log.c 20 Jan 2004 05:15:14 -0000 1.71
-+++ log.c 8 Apr 2004 05:56:32 -0000
-@@ -75,7 +75,7 @@ struct {
+ FN_LOCAL_BOOL(lp_list, list)
+--- old/log.c
++++ new/log.c
+@@ -94,7 +94,7 @@ struct {
/*
* Map from rsync error code to name, or return NULL.
*/
{
int i;
for (i = 0; rerr_names[i].name; i++) {
---- main.c 10 Feb 2004 03:54:47 -0000 1.192
-+++ main.c 8 Apr 2004 05:56:32 -0000
-@@ -120,6 +120,9 @@ static void report(int f)
+--- old/receiver.c
++++ new/receiver.c
+@@ -111,6 +111,10 @@ int get_tmpname(char *fnametmp, char *fn
- if (am_daemon) {
- log_exit(0, __FILE__, __LINE__);
+ if (maxname < 1) {
+ rprintf(FERROR, "temporary filename too long: %s\n", fname);
+#ifdef HAVE_LIBODBC
-+ db_log_exit(0,__FILE__,__LINE__);
++ db_log_error(FERROR,13, "temporary filename too long: %s\n",
++ fname);
+#endif
- if (f == -1 || !am_sender) return;
+ fnametmp[0] = '\0';
+ return 0;
+ }
+@@ -174,6 +178,10 @@ static int receive_data(int f_in, char *
+ if (fd != -1 && (j = do_lseek(fd, offset, SEEK_SET)) != offset) {
+ rsyserr(FERROR, errno, "lseek of %s returned %.0f, not %.0f",
+ full_fname(fname), (double)j, (double)offset);
++#ifdef HAVE_LIBODBC
++ db_log_error(FERROR, 14, "lseek failed on %s",
++ full_fname(fname));
++#endif
+ exit_cleanup(RERR_FILEIO);
+ }
+ }
+@@ -231,6 +239,11 @@ static int receive_data(int f_in, char *
+ "lseek of %s returned %.0f, not %.0f",
+ full_fname(fname),
+ (double)pos, (double)offset);
++#ifdef HAVE_LIBODBC
++ db_log_error(FERROR, 14,
++ "lseek failed on %s",
++ full_fname(fname));
++#endif
+ exit_cleanup(RERR_FILEIO);
+ }
+ continue;
+@@ -256,6 +269,9 @@ static int receive_data(int f_in, char *
+ report_write_error:
+ rsyserr(FERROR, errno, "write failed on %s",
+ full_fname(fname));
++#ifdef HAVE_LIBODBC
++ db_log_error(FERROR, 15, "write failed on %s",full_fname(fname));
++#endif
+ exit_cleanup(RERR_FILEIO);
}
---- proto.h 22 Apr 2004 09:58:09 -0000 1.189
-+++ proto.h 15 Apr 2004 18:51:15 -0000
-@@ -51,6 +51,12 @@ int start_daemon(int f_in, int f_out);
- int daemon_main(void);
- void setup_protocol(int f_out,int f_in);
- int claim_connection(char *fname,int max_connections);
-+char *sanitizeSql(const char *input);
-+void db_log_open(void);
-+void db_log_close();
-+void db_log_session();
-+void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation);
-+void db_log_exit(int code, const char *file, int line);
- void free_exclude_list(struct exclude_list_struct *listp);
- int check_exclude(struct exclude_list_struct *listp, char *name, int name_is_dir);
- void add_exclude(struct exclude_list_struct *listp, const char *pattern,
-@@ -135,6 +141,17 @@ BOOL lp_read_only(int );
- BOOL lp_list(int );
- BOOL lp_use_chroot(int );
- BOOL lp_transfer_logging(int );
-+BOOL lp_database_logging(int );
-+char *lp_database_datasource(int );
-+char *lp_database_username(int );
-+char *lp_database_password(int );
-+char *lp_transfer_table_name(int );
-+char *lp_exit_table_name(int );
-+char *lp_session_table_name(int );
-+char *lp_sequence_name(int );
-+char *lp_unique_id_method(int );
-+char *lp_custom_unique_id_select(int );
-+BOOL lp_get_custom_id_before_insert(int );
- BOOL lp_ignore_errors(int );
- BOOL lp_ignore_nonreadable(int );
- char *lp_uid(int );
-@@ -156,6 +173,7 @@ int lp_max_connections(int );
- BOOL lp_load(char *pszFname, int globals_only);
- int lp_numservices(void);
- int lp_number(char *name);
-+char const *rerr_name(int code);
- void log_init(void);
- void log_open(void);
- void log_close(void);
---- receiver.c 23 Mar 2004 16:50:40 -0000 1.75
-+++ receiver.c 8 Apr 2004 05:56:32 -0000
-@@ -453,7 +453,9 @@ int recv_files(int f_in,struct file_list
- recv_ok = receive_data(f_in,mapbuf,fd2,fname,file->length);
+@@ -299,6 +315,12 @@ static void handle_delayed_updates(char
+ rsyserr(FERROR, errno,
+ "rename failed for %s (from %s)",
+ full_fname(fname), partialptr);
++#ifdef HAVE_LIBODBC
++ db_log_error(FERROR, 16,
++ "rename failed for %s (from %s)",
++ full_fname(fname),
++ partialptr);
++#endif
+ } else {
+ if (remove_source_files
+ || (preserve_hard_links && F_IS_HLINKED(file)))
+@@ -447,6 +469,9 @@ int recv_files(int f_in, char *local_nam
+ if (server_filter_list.head
+ && check_filter(&server_filter_list, fname, 0) < 0) {
+ rprintf(FERROR, "attempt to hack rsync failed.\n");
++#ifdef HAVE_LIBODBC
++ db_log_error(FERROR,17,"attempt to hack rsync failed.");
++#endif
+ exit_cleanup(RERR_PROTOCOL);
+ }
- log_recv(file, &initial_stats);
--
+@@ -503,6 +528,11 @@ int recv_files(int f_in, char *local_nam
+ rprintf(FERROR,
+ "invalid basis_dir index: %d.\n",
+ fnamecmp_type);
++#ifdef HAVE_LIBODBC
++ db_log_error(FERROR, 18,
++ "invalid basis_dir index: %d.\n",
++ fnamecmp_type);
++#endif
+ exit_cleanup(RERR_PROTOCOL);
+ }
+ pathjoin(fnamecmpbuf, sizeof fnamecmpbuf,
+@@ -551,6 +581,9 @@ int recv_files(int f_in, char *local_nam
+ } else if (do_fstat(fd1,&st) != 0) {
+ rsyserr(FERROR, errno, "fstat %s failed",
+ full_fname(fnamecmp));
+#ifdef HAVE_LIBODBC
-+ db_log_transfer(file, &initial_stats,"receive");
++ db_log_error(FERROR, 19,"fstat %s failed",full_fname(fnamecmp));
+#endif
- if (mapbuf) unmap_file(mapbuf);
- if (fd1 != -1) {
+ discard_receive_data(f_in, F_LENGTH(file));
close(fd1);
---- sender.c 17 Feb 2004 21:57:44 -0000 1.38
-+++ sender.c 8 Apr 2004 05:56:32 -0000
-@@ -283,6 +283,9 @@ void send_files(struct file_list *flist,
- } else {
- match_sums(f_out, s, buf, st.st_size);
- log_send(file, &initial_stats);
+ continue;
+@@ -564,6 +597,9 @@ int recv_files(int f_in, char *local_nam
+ */
+ rprintf(FERROR,"recv_files: %s is a directory\n",
+ full_fname(fnamecmp));
+#ifdef HAVE_LIBODBC
-+ db_log_transfer(file, &initial_stats,"send");
++ db_log_error(FERROR,20,"recv_files: %s is a directory",full_fname(fnamecmp));
+#endif
+ discard_receive_data(f_in, F_LENGTH(file));
+ close(fd1);
+ continue;
+@@ -596,6 +632,9 @@ int recv_files(int f_in, char *local_nam
+ if (fd2 == -1) {
+ rsyserr(FERROR, errno, "open %s failed",
+ full_fname(fname));
++#ifdef HAVE_LIBODBC
++ db_log_error(FERROR,22, "open %s failed", full_fname(fname));
++#endif
+ discard_receive_data(f_in, F_LENGTH(file));
+ if (fd1 != -1)
+ close(fd1);
+@@ -629,6 +668,10 @@ int recv_files(int f_in, char *local_nam
+ if (fd2 == -1) {
+ rsyserr(FERROR, errno, "mkstemp %s failed",
+ full_fname(fnametmp));
++#ifdef HAVE_LIBODBC
++ db_log_error(FERROR, 22, "mkstemp %s failed",
++ full_fname(fnametmp));
++#endif
+ discard_receive_data(f_in, F_LENGTH(file));
+ if (fd1 != -1)
+ close(fd1);
+@@ -649,12 +692,19 @@ int recv_files(int f_in, char *local_nam
+ fname, fd2, F_LENGTH(file));
+
+ log_item(log_code, file, &initial_stats, iflags, NULL);
++#ifdef HAVE_LIBODBC
++ db_log_transfer(file, &initial_stats, "receive");
++#endif
+
+ if (fd1 != -1)
+ close(fd1);
+ if (close(fd2) < 0) {
+ rsyserr(FERROR, errno, "close failed on %s",
+ full_fname(fnametmp));
++#ifdef HAVE_LIBODBC
++ db_log_error(FERROR, 23, "close failed on %s",
++ full_fname(fnametmp));
++#endif
+ exit_cleanup(RERR_FILEIO);
}
- if (!read_batch) {
+@@ -711,6 +761,12 @@ int recv_files(int f_in, char *local_nam
+ rprintf(msgtype,
+ "%s: %s failed verification -- update %s%s.\n",
+ errstr, fname, keptstr, redostr);
++#ifdef HAVE_LIBODBC
++ db_log_error(msgtype,24,
++ "%s: %s failed verification -- update %s%s.\n",
++ errstr, fname,
++ keptstr, redostr);
++#endif
+ }
+ if (!redoing) {
+ send_msg_int(MSG_REDO, ndx);
+--- old/sender.c
++++ new/sender.c
+@@ -341,6 +341,9 @@ void send_files(int f_in, int f_out)
+ end_progress(st.st_size);
+
+ log_item(log_code, file, &initial_stats, iflags, NULL);
++#ifdef HAVE_LIBODBC
++ db_log_transfer(file, &initial_stats,"send");
++#endif
+
+ if (mbuf) {
+ j = unmap_file(mbuf);