Fixed failing hunks.
[rsync-patches.git] / ODBC-dblog.diff
1 Add support for logging daemon messages to an SQL database.
2
3 To use this patch, run these commands for a successful build:
4
5     patch -p1 <patches/ODBC-dblog.diff
6     ./prepare-source
7     ./configure --enable-ODBC
8     make
9
10 See the newly-created file "instructions" for more info.
11
12 --- old/Makefile.in
13 +++ new/Makefile.in
14 @@ -32,7 +32,7 @@ LIBOBJ=lib/wildmatch.o lib/compat.o lib/
15  ZLIBOBJ=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
16         zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o
17  OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
18 -       util.o main.o checksum.o match.o syscall.o log.o backup.o
19 +       util.o main.o checksum.o match.o syscall.o log.o backup.o @EXTRA_OBJECT@
20  OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o \
21         fileio.o batch.o clientname.o chmod.o acls.o xattrs.o
22  OBJS3=progress.o pipe.o
23 --- old/cleanup.c
24 +++ new/cleanup.c
25 @@ -26,6 +26,7 @@ extern int am_server;
26  extern int am_daemon;
27  extern int io_error;
28  extern int keep_partial;
29 +extern int am_generator;
30  extern int log_got_error;
31  extern char *partial_dir;
32  extern char *logfile_name;
33 @@ -173,8 +174,13 @@ NORETURN void _exit_cleanup(int code, co
34                                 code = exit_code = RERR_PARTIAL;
35                 }
36  
37 -               if (code || am_daemon || (logfile_name && (am_server || !verbose)))
38 +               if (code || am_daemon || (logfile_name && (am_server || !verbose))) {
39                         log_exit(code, file, line);
40 +#ifdef HAVE_LIBODBC
41 +                       db_log_exit(code, file, line);
42 +                       db_log_close();
43 +#endif
44 +               }
45  
46                 /* FALLTHROUGH */
47  #include "case_N.h"
48 --- old/clientserver.c
49 +++ new/clientserver.c
50 @@ -396,6 +396,9 @@ static int rsync_module(int f_in, int f_
51                    XFLG_ABS_IF_SLASH | XFLG_OLD_PREFIXES);
52  
53         log_init(1);
54 +#ifdef HAVE_LIBODBC
55 +       db_log_open();
56 +#endif
57  
58  #ifdef HAVE_PUTENV
59         if (*lp_prexfer_exec(i) || *lp_postxfer_exec(i)) {
60 @@ -642,6 +645,9 @@ static int rsync_module(int f_in, int f_
61                         rprintf(FLOG, "rsync %s %s from %s@%s (%s)\n",
62                                 am_sender ? "on" : "to",
63                                 request, auth_user, host, addr);
64 +#ifdef HAVE_LIBODBC
65 +                       db_log_session();
66 +#endif
67                 } else {
68                         rprintf(FLOG, "rsync %s %s from %s (%s)\n",
69                                 am_sender ? "on" : "to",
70 --- old/configure.in
71 +++ new/configure.in
72 @@ -643,6 +643,12 @@ if test x"$with_included_popt" != x"yes"
73      AC_CHECK_LIB(popt, poptGetContext, , [with_included_popt=yes])
74  fi
75  
76 +AC_ARG_ENABLE(ODBC, AC_HELP_STRING([--enable-ODBC], [compile in support for ODBC database logging]),
77 +    [ AC_CHECK_HEADERS(sql.h sqlext.h sqltypes.h)
78 +    AC_CHECK_LIB(odbc,SQLExecDirect)
79 +    EXTRA_OBJECT="$EXTRA_OBJECT dblog.o"
80 +    AC_SUBST(EXTRA_OBJECT) ])
81 +
82  AC_MSG_CHECKING([whether to use included libpopt])
83  if test x"$with_included_popt" = x"yes"; then
84      AC_MSG_RESULT($srcdir/popt)
85 --- old/db_log_error-list.txt
86 +++ new/db_log_error-list.txt
87 @@ -0,0 +1,35 @@
88 +error type             description
89 +0                      not an error.
90 +1                      authentication
91 +2                      file/dir deletion failed
92 +3                      connection closed
93 +4                      read error
94 +5                      multiplexing overflow
95 +6                      unexpected tag
96 +7                      over long v-string received
97 +8                      invalid block length
98 +9                      invalid checksum length
99 +10                     invalid remainder length
100 +11                     failed to write error
101 +12                     attempting to send over-long vstring
102 +13                     temporary filename too long
103 +14                     lseek failed
104 +15                     write failed
105 +16                     rename failed
106 +17                     rsync hack failed
107 +18                     "invalid basis_dir index
108 +19                     fstat failed
109 +20                     is a directory
110 +21                     open file failed
111 +22                     mkstemp failed
112 +23                     close failed
113 +24                     failed verification
114 +25                     IO error, skipping deletion.
115 +26                     directory creation failed
116 +27                     ignoring unsafe symbolic link
117 +28                     symbolic link failed
118 +29                     mknod failed
119 +30                     failed to stat
120 +31                     unlink
121 +32                     failed to open file/directory
122 +33                     open?
123 --- old/dblog-tables-mysql.sql
124 +++ new/dblog-tables-mysql.sql
125 @@ -0,0 +1,64 @@
126 +drop table transfer;
127 +drop table exit;
128 +drop table session;
129 +
130 +CREATE TABLE session (
131 +       id                      int auto_increment NOT NULL,
132 +       date                    timestamp NOT NULL,
133 +       ip_address              varchar(15) NOT NULL,
134 +       username                varchar(20) NOT NULL,
135 +       module_name             varchar(20) NOT NULL,
136 +       module_path             varchar(255) NOT NULL,
137 +       process_id              int NOT NULL,
138 +       Primary Key (id)
139 +);
140 +
141 +CREATE TABLE transfer (
142 +       id                      int auto_increment NOT NULL,
143 +       session_id              int NOT NULL,
144 +       date                    timestamp NOT NULL,
145 +       file_name               varchar(255) NOT NULL,
146 +       file_size               bigint NOT NULL,
147 +       bytes_transferred       bigint NOT NULL,
148 +       checksum_bytes_transferred bigint NOT NULL,
149 +       operation               varchar(20),
150 +       Primary Key (id),
151 +       foreign key (session_id) references session (id)
152 +);
153 +
154 +CREATE TABLE exit (
155 +       id                      int auto_increment NOT NULL,
156 +       session_id              int NOT NULL,
157 +       date                    timestamp NOT NULL,
158 +       total_bytes_written     bigint NOT NULL,
159 +       total_bytes_read        bigint NOT NULL,
160 +       total_size              bigint NOT NULL,
161 +       error_text              varchar(128) NOT NULL,
162 +       error_code              int NOT NULL,
163 +       error_file              varchar(64) NOT NULL,
164 +       error_line              int NOT NULL,
165 +       process_id              int NOT NULL,
166 +       Primary Key (id),
167 +       foreign key (session_id) references session (id)
168 +);
169 +
170 +CREATE TABLE error (
171 +       id                      int auto_increment NOT NULL,
172 +       session_id              int NOT NULL,
173 +       date                    timestamp NOT NULL,
174 +       logcode                 bigint NOT NULL,
175 +       error_number            bigint NOT NULL,
176 +       error_text              varchar(512),
177 +       PrimaryKey (id),
178 +       foreign key (session_id) references session (id)
179 +);
180 +
181 +CREATE TABLE delete (
182 +       id                      serial NOT NULL,
183 +       session_id              int NOT NULL,
184 +       date                    timestamp NOT NULL,
185 +       path                    varchar(512) NOT NULL,
186 +       mode                    int NOT NULL,
187 +       PrimaryKey (id),
188 +       foreign key (session_id) references session (id)
189 +);
190 --- old/dblog-tables-postgresql.sql
191 +++ new/dblog-tables-postgresql.sql
192 @@ -0,0 +1,67 @@
193 +drop table transfer;
194 +drop table exit;
195 +drop table session;
196 +drop sequence session_id_seq;
197 +create sequence session_id_seq;
198 +
199 +CREATE TABLE "session" (
200 +       "id"                    int NOT NULL,
201 +       "date"                  timestamp NOT NULL default now(),
202 +       "ip_address"            varchar(15) NOT NULL,
203 +       "username"              varchar(20) NOT NULL,
204 +       "module_name"           varchar(20) NOT NULL,
205 +       "module_path"           varchar(255) NOT NULL,
206 +       "process_id"            int NOT NULL,
207 +       Primary Key (id)
208 +);
209 +
210 +CREATE TABLE "transfer" (
211 +       "id"                    serial NOT NULL,
212 +       "session_id"            int NOT NULL,
213 +       "date"                  timestamp NOT NULL default now(),
214 +       "file_name"             varchar(512) NOT NULL,
215 +       "file_size"             bigint NOT NULL,
216 +       "bytes_transferred"     bigint NOT NULL,
217 +       "checksum_bytes_transferred" bigint NOT NULL,
218 +       "operation"             varchar(20),
219 +       Primary Key (id),
220 +       foreign key (session_id) references session (id)
221 +);
222 +
223 +CREATE TABLE "exit" (
224 +       "id"                    serial NOT NULL,
225 +       "session_id"            int NOT NULL,
226 +       "date"                  timestamp NOT NULL default now(),
227 +       "total_bytes_written"   bigint NOT NULL,
228 +       "total_bytes_read"      bigint NOT NULL,
229 +       "total_size"            bigint NOT NULL,
230 +       "error_text"            varchar(128) NOT NULL,
231 +       "error_code"            int NOT NULL,
232 +       "error_file"            varchar(64) NOT NULL,
233 +       "error_line"            int NOT NULL,
234 +       "process_id"            int NOT NULL,
235 +       Primary Key (id),
236 +       foreign key (session_id) references session (id)
237 +);
238 +
239 +CREATE TABLE "error" (
240 +       "id"                    serial NOT NULL,
241 +       "session_id"            int NOT NULL,
242 +       "date"                  timestamp NOT NULL default now(),
243 +       "logcode"               int NOT NULL,
244 +       "error_number"          int NOT NULL,
245 +       "error_text"            varchar(512),
246 +       Primary Key (id),
247 +       foreign key (session_id) references session (id)
248 +
249 +);
250 +
251 +CREATE TABLE "delete" (
252 +       "id"                    serial NOT NULL,
253 +       "session_id"            int NOT NULL,
254 +       "date"                  timestamp NOT NULL default now(),
255 +       "path"                  varchar(512) NOT NULL,
256 +       "mode"                  int NOT NULL,
257 +       Primary Key (id),
258 +       foreign key (session_id) references session (id)
259 +);
260 --- old/dblog.c
261 +++ new/dblog.c
262 @@ -0,0 +1,549 @@
263 +/*
264 + *  ODBC Database logging functions
265 + *
266 + *  Written by Steve Sether, April 2004
267 + *  steve@vellmont.com
268 + */
269 +
270 +#include "rsync.h"
271 +
272 +#ifdef HAVE_SQL_H
273 +#include <sql.h>
274 +#else
275 +#ifdef HAVE_ODBC_SQL_H
276 +#include <odbc/sql.h>
277 +#endif
278 +#endif
279 +
280 +#ifdef HAVE_SQLEXT_H
281 +#include <sqlext.h>
282 +#else
283 +#ifdef HAVE_ODBC_SQLEXT_H
284 +#include <odbc/sqlext.h>
285 +#endif
286 +#endif
287 +
288 +#ifdef HAVE_SQLTYPES_H
289 +#include <sqltypes.h>
290 +#else
291 +#ifdef HAVE_ODBC_SQLTYPES_H
292 +#include <odbc/sqltypes.h>
293 +#endif
294 +#endif
295 +
296 +SQLHENV db_environ_handle;                     /* Handle ODBC environment */
297 +long result;                                   /* result of functions */
298 +SQLHDBC db_handle_g = NULL;                    /* database connection handle for generator*/
299 +SQLHDBC db_handle_r = NULL;                    /* database connection handle for sender */
300 +SQLHSTMT sql_statement_handle_g;               /* SQL statement handle for generator*/
301 +SQLHSTMT sql_statement_handle_r;               /* SQL statement handle for receiver*/
302 +extern int am_daemon;
303 +extern int am_sender;
304 +extern int am_generator;
305 +extern char *auth_user;
306 +extern int module_id;
307 +extern int dry_run;
308 +
309 +
310 +char sql_status[10];                           /* Status SQL */
311 +SQLINTEGER V_OD_err, V_OD_rowanz, V_OD_id;
312 +SQLSMALLINT V_OD_mlen, V_OD_colanz;
313 +char V_OD_msg[200], V_OD_buffer[200];
314 +SQLINTEGER session_id;
315 +
316 +
317 +/* This function simply removes invalid characters from the SQL statement
318 + * to prevent SQL injection attacks. */
319 +char *sanitizeSql(const char *input)
320 +{
321 +       char *out, *ptr;
322 +       const char *c;
323 +
324 +       if (strlen(input) > ((~(unsigned int)0)>>1)-3)
325 +               return 0;
326 +       if (!(out = ptr = new_array(char, strlen(input) * 2 + 1)))
327 +               return 0;
328 +
329 +       for (c = input;  *c;  c++) {
330 +               switch (*c) {
331 +               case '\'':
332 +                       *ptr++ = '\'';
333 +                       *ptr++ = '\'';
334 +                       break;
335 +               case '\b':
336 +                       *ptr++ = '\\';
337 +                       *ptr++ = 'b';
338 +                       break;
339 +               case '\n':
340 +                       *ptr++ = '\\';
341 +                       *ptr++ = 'n';
342 +                       break;
343 +               case '\r':
344 +                       *ptr++ = '\\';
345 +                       *ptr++ = 'r';
346 +                       break;
347 +               case '\t':
348 +                       *ptr++ = '\\';
349 +                       *ptr++ = 't';
350 +                       break;
351 +               default:
352 +                       *ptr++ = *c;
353 +                       break;
354 +               }
355 +       }
356 +       *ptr = '\0';
357 +       return out;
358 +}
359 +
360 +void db_log_open(void)
361 +{
362 +       if (!lp_database_logging(module_id))
363 +               return;
364 +
365 +       /* get ODBC environment handle */
366 +       result = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&db_environ_handle);
367 +       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
368 +               rprintf(FERROR, "Error: couldn't get database environment handle\n");
369 +               return;
370 +       }
371 +
372 +       /* Setting database enviroment */
373 +       result = SQLSetEnvAttr(db_environ_handle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
374 +       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
375 +               rprintf(FERROR, "Error: couldn't set database environment.\n");
376 +               SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
377 +               db_environ_handle = NULL;
378 +               return;
379 +       }
380 +       if (db_handle_g == NULL) {
381 +               /* Get a database handle for the generator*/
382 +               result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle_g);
383 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
384 +                       rprintf(FERROR, "Error: couldn't allocate database handle for generator\n");
385 +                       SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
386 +                       db_environ_handle = NULL;
387 +                       return;
388 +               }
389 +
390 +               /* Set connection attributes for the generator db connection */
391 +               SQLSetConnectAttr(db_handle_g, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
392 +
393 +               /* get the database connection for the generator. */
394 +               result = SQLConnect(db_handle_g, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS,
395 +                   (SQLCHAR*) lp_database_username(module_id), SQL_NTS,
396 +                   (SQLCHAR*) lp_database_password(module_id), SQL_NTS);
397 +
398 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
399 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_g, 1,
400 +                           sql_status, &V_OD_err, V_OD_msg, 100, &V_OD_mlen);
401 +                       rprintf(FERROR,"Error Connecting to Database (generator) %s\n",V_OD_msg);
402 +                       SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g);
403 +                       db_handle_g = NULL;
404 +                       SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
405 +                       db_environ_handle = NULL;
406 +                       return;
407 +               }
408 +               rprintf(FLOG,"Connected to database for generator!\n");
409 +       } else {
410 +               rprintf(FERROR,"Already connected to database for generator\n");
411 +       }
412 +       if (db_handle_r == NULL) {
413 +               /* Get a database handle for the receiver */
414 +               result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle_r);
415 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
416 +                       rprintf(FERROR, "Error: couldn't allocate database handle for receiver\n");
417 +                       SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
418 +                       db_environ_handle = NULL;
419 +                       return;
420 +               }
421 +
422 +               /* Set connection attributes for the receiver db connection */
423 +               SQLSetConnectAttr(db_handle_r, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
424 +
425 +               /* get the generator connection for the receiver. */
426 +               result = SQLConnect(db_handle_r, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS,
427 +                   (SQLCHAR*) lp_database_username(module_id), SQL_NTS,
428 +                   (SQLCHAR*) lp_database_password(module_id), SQL_NTS);
429 +
430 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
431 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_r,1,
432 +                           sql_status, &V_OD_err,V_OD_msg,100,&V_OD_mlen);
433 +                       rprintf(FERROR,"Error Connecting to Database (receiver) %s\n",V_OD_msg);
434 +                       SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r);
435 +                       db_handle_r = NULL;
436 +                       SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
437 +                       db_environ_handle = NULL;
438 +                       return;
439 +               }
440 +               rprintf(FLOG,"Connected to database for receiver!\n");
441 +       } else {
442 +               rprintf(FERROR,"Already connected to database for receiver\n");
443 +       }
444 +
445 +       /* get SQL statement handle for generator */
446 +       result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle_g, &sql_statement_handle_g);
447 +       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
448 +               SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_g,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
449 +               rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg);
450 +               SQLDisconnect(db_handle_g);
451 +               SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g);
452 +               db_handle_g = NULL;
453 +               SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
454 +               db_environ_handle = NULL;
455 +               return;
456 +       }
457 +
458 +       /* get SQL statement handle for receiver */
459 +       result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle_r, &sql_statement_handle_r);
460 +       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
461 +               SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_r,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
462 +               rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg);
463 +               SQLDisconnect(db_handle_r);
464 +               SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r);
465 +               db_handle_r = NULL;
466 +               SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
467 +               db_environ_handle = NULL;
468 +               return;
469 +       }
470 +}
471 +
472 +void db_log_close()
473 +{
474 +       if (!lp_database_logging(module_id))
475 +               return;
476 +
477 +       if (am_generator) {
478 +               if (sql_statement_handle_g != NULL) {
479 +                       /* free the statement handle first */
480 +                       SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle_g);
481 +                       sql_statement_handle_g = NULL;
482 +               } else {
483 +                       rprintf(FERROR,"No generator sql statement handle to close\n");
484 +               }
485 +
486 +               if (db_handle_g != NULL) {
487 +                       /* disconnect, and free the database handle. */
488 +                       SQLDisconnect(db_handle_g);
489 +                       SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g);
490 +                       db_handle_g = NULL;
491 +               } else {
492 +                       rprintf(FERROR,"Generator database connection already closed\n");
493 +               }
494 +       } else { /* must be receiver */
495 +               if (sql_statement_handle_r != NULL) {
496 +                       /* free the statement handle first */
497 +                       SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle_r);
498 +                       sql_statement_handle_r = NULL;
499 +               } else {
500 +                       rprintf(FERROR,"No receiver sql statement handle to close\n");
501 +               }
502 +
503 +               if (db_handle_r != NULL) {
504 +                       /* disconnect, and free the database handle. */
505 +                       SQLDisconnect(db_handle_r);
506 +                       SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r);
507 +                       db_handle_r = NULL;
508 +               } else {
509 +                       rprintf(FERROR,"Receiver database connection already closed\n");
510 +               }
511 +       }
512 +
513 +       if (db_environ_handle != NULL) {
514 +               /* free the environment handle */
515 +               SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
516 +               db_environ_handle = NULL;
517 +       } else {
518 +               rprintf(FERROR,"No environment handle to close\n");
519 +       }
520 +}
521 +
522 +static long get_unique_session_id()
523 +{
524 +       long unique;
525 +       char strSqlStatement[1024];
526 +       SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
527 +       SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
528 +
529 +       if (db_handle != NULL) {
530 +               /* choose the appropriate select statement based upon which DBMS we're using.
531 +                * different datbases use different methods to get a unique ID.  Some use a counter
532 +                * object (sequence), others use an auto increment datatype and have a method
533 +                * to get the last ID inserted using this connection. */
534 +               if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0) {
535 +                       snprintf(strSqlStatement, sizeof strSqlStatement,
536 +                           "SELECT NEXTVAL('%s');", lp_sequence_name(module_id));
537 +               } else if (strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0) {
538 +                       snprintf(strSqlStatement, sizeof strSqlStatement,
539 +                           "SELECT %s.NEXTVAL FROM dual;", lp_sequence_name(module_id));
540 +               } else if (strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0) {
541 +                       snprintf(strSqlStatement, sizeof strSqlStatement,
542 +                           "VALUES NEXTVAL FOR %s;",lp_sequence_name(module_id));
543 +               } else if (strcmp(lp_unique_id_method(module_id),"last_insert_id") == 0) { /* MySql */
544 +                       snprintf(strSqlStatement, sizeof strSqlStatement,
545 +                           "SELECT LAST_INSERT_ID()");
546 +               } else if (strcmp(lp_unique_id_method(module_id),"@@IDENTITY") == 0) { /* Sybase */
547 +                       snprintf(strSqlStatement, sizeof strSqlStatement,
548 +                           "SELECT @@IDENTITY");
549 +               } else if (strcmp(lp_unique_id_method(module_id),"custom") == 0){ /* Users custom statement */
550 +                       snprintf(strSqlStatement, sizeof strSqlStatement,
551 +                           lp_custom_unique_id_select(module_id));
552 +               }
553 +
554 +               /* bind the 1st column to unique */
555 +               SQLBindCol(sql_statement_handle,1,SQL_C_LONG,&unique,150,&V_OD_err);
556 +               /* execute the SQL statement */
557 +               result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
558 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
559 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
560 +                       rprintf(FERROR,"Error at get_sequence:  Error in Select! %s %s\n",strSqlStatement,V_OD_msg);
561 +               } else {
562 +                       result = SQLFetch(sql_statement_handle);
563 +                       if (result != SQL_NO_DATA && unique != 0) {
564 +                               rprintf(FINFO,"Got unique sequence! %ld\n",unique);
565 +                       } else {
566 +                               rprintf(FERROR,"Error at get_sequence:  Didn't get unique session ID\n");
567 +                       }
568 +                       /* Close the cursor so the statement can be re-used */
569 +                       result = SQLFreeStmt(sql_statement_handle,SQL_CLOSE);
570 +                       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
571 +                               SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
572 +                               rprintf(FERROR,"Error at get_sequence: Error in closing SQL statement handle %s\n",V_OD_msg);
573 +                               return unique;
574 +                       }
575 +                       return unique;
576 +               }
577 +       }
578 +       rprintf(FERROR,"Error at get_sequence: Not connected to database\n");
579 +       return -1;
580 +}
581 +
582 +void db_log_session()
583 +{
584 +       char strSqlStatement[1024];
585 +       int gotSessionID = 0;
586 +       SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
587 +       SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
588 +
589 +       if (!lp_database_logging(module_id))
590 +               return;
591 +
592 +       if (db_handle != NULL) {
593 +               /* if we're using a sequence via the nextval command to
594 +                * get a unique ID, we need to get it before we do the
595 +                * insert. We also get the unique ID  now if custom,
596 +                * and get_custom_id_before_insert is set. */
597 +               if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0
598 +                || strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0
599 +                || strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0
600 +                || (strcmp(lp_unique_id_method(module_id),"custom") == 0
601 +                 && lp_get_custom_id_before_insert(module_id))) {
602 +                       session_id = get_unique_session_id();
603 +                       gotSessionID = 1;
604 +                       snprintf(strSqlStatement, sizeof strSqlStatement,
605 +                           "INSERT INTO %s (id, date, ip_address, username, module_name, module_path, process_id) VALUES ('%ld', '%s', '%s', '%s','%s','%s','%d');",
606 +                           lp_session_table_name(module_id), session_id, timestring(time(NULL)), client_addr(0),
607 +                           auth_user, lp_name(module_id), lp_path(module_id), getpid());
608 +               } else {
609 +                       /* Otherwise the ID gets created automatically, and we get the ID it used after the insert. */
610 +                       snprintf(strSqlStatement, sizeof strSqlStatement,
611 +                           "INSERT INTO %s (date, ip_address, username, module_name, module_path, process_id) VALUES ('%s', '%s', '%s', '%s','%s','%d');",
612 +                           lp_session_table_name(module_id), timestring(time(NULL)), client_addr(0), auth_user,
613 +                           lp_name(module_id), lp_path(module_id), getpid());
614 +               }
615 +
616 +               /* Insert the new session into the database */
617 +               result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
618 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
619 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
620 +                       rprintf(FERROR,"Error at db_log_session: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
621 +               }
622 +
623 +               /* close the cursor so the statement handle can be re-used. */
624 +               result = SQLFreeStmt(sql_statement_handle,SQL_CLOSE);
625 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
626 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
627 +                       rprintf(FERROR,"Error in resetting SQL statement handle %s\n",V_OD_msg);
628 +               }
629 +               /* get the session ID for databases that give the unique ID after an insert */
630 +               if (gotSessionID == 0) {
631 +                       session_id = get_unique_session_id();
632 +               }
633 +       } else {
634 +               rprintf(FERROR,"Error at db_log_session:  Not connected to database!\n");
635 +       }
636 +}
637 +
638 +void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation)
639 +{
640 +       extern struct stats stats;
641 +       char strSqlStatement[2048];
642 +       char strFileName[MAXPATHLEN];
643 +       char *strFileNamePtr;
644 +       char strFileSize[255];
645 +       int64 intBytesTransferred;
646 +       int64 intCheckSumBytes;
647 +       SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
648 +       SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
649 +
650 +       if (!lp_database_logging(module_id))
651 +               return;
652 +
653 +       if (db_handle != NULL) {
654 +               strFileNamePtr = f_name(file, NULL);
655 +               if (am_sender && file->dir.root) {
656 +                       pathjoin(strFileName, sizeof strFileName,
657 +                                file->dir.root, strFileNamePtr);
658 +                       strFileNamePtr = strFileName;
659 +               }
660 +               clean_fname(strFileNamePtr, 0);
661 +               if (*strFileNamePtr == '/')
662 +                       strFileNamePtr++;
663 +
664 +               snprintf(strFileSize, sizeof strFileSize, "%.0f", (double)F_LENGTH(file));
665 +               if (am_sender) {
666 +                       intBytesTransferred = stats.total_written - initial_stats->total_written;
667 +               } else {
668 +                       intBytesTransferred = stats.total_read - initial_stats->total_read;
669 +               }
670 +
671 +               if (!am_sender) {
672 +                       intCheckSumBytes = stats.total_written - initial_stats->total_written;
673 +               } else {
674 +                       intCheckSumBytes = stats.total_read - initial_stats->total_read;
675 +               }
676 +
677 +               snprintf(strSqlStatement, sizeof strSqlStatement,
678 +                   "INSERT INTO %s (session_id,date, file_name, file_size, bytes_transferred, checksum_bytes_transferred, operation) VALUES ('%ld','%s','%s','%s','%Ld','%Ld','%s');",
679 +                   lp_transfer_table_name(module_id), session_id, timestring(time(NULL)),
680 +                   sanitizeSql(strFileNamePtr), strFileSize, intBytesTransferred,
681 +                   intCheckSumBytes, operation);
682 +               result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
683 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
684 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
685 +                       rprintf(FERROR,"Error at db_log_transfer:  Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
686 +                       if (result == SQL_INVALID_HANDLE)
687 +                               rprintf(FERROR,"INVALID HANDLE\n");
688 +               }
689 +       } else {
690 +               rprintf(FERROR,"Error at db_log_transfer: Not connected to database!\n");
691 +       }
692 +}
693 +
694 +void db_log_exit(int code, const char *file, int line)
695 +{
696 +       char strSqlStatement[2048];
697 +       const char *error_text;
698 +       extern struct stats stats;
699 +       SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
700 +       SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
701 +
702 +       if (!lp_database_logging(module_id))
703 +               return;
704 +
705 +       if (db_handle != NULL) {
706 +               if (code != 0) {
707 +                       error_text = rerr_name(code);
708 +                       if (!error_text) {
709 +                               error_text = "unexplained error";
710 +                       }
711 +               } else {
712 +                       error_text = "";
713 +               }
714 +               snprintf(strSqlStatement, sizeof strSqlStatement,
715 +                   "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');",
716 +                   lp_exit_table_name(module_id), session_id, timestring(time(NULL)), stats.total_written,
717 +                   stats.total_read, stats.total_size, error_text, code, file, line, getpid());
718 +
719 +               result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
720 +
721 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
722 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
723 +                       rprintf(FERROR,"Error at db_log_exit: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
724 +               }
725 +       } else {
726 +               rprintf(FERROR,"Error at db_log_exit: Not connected to database!\n");
727 +       }
728 +}
729 +
730 +void db_log_delete(char *fname, int mode)
731 +{
732 +       char strSqlStatement[2048];
733 +       SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
734 +       SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
735 +
736 +       if (!am_daemon || dry_run || !lp_database_logging(module_id))
737 +               return;
738 +
739 +       if (db_handle != NULL) {
740 +               snprintf(strSqlStatement, sizeof strSqlStatement,
741 +                   "INSERT INTO %s (session_id, date, path, mode) VALUES ('%ld','%s','%s','%d');",
742 +                   lp_delete_table_name(module_id), session_id, timestring(time(NULL)), sanitizeSql(fname), mode);
743 +
744 +               result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
745 +
746 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
747 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
748 +                       rprintf(FERROR,"Error at db_log_delete: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
749 +               }
750 +       } else {
751 +               rprintf(FERROR,"Error at db_log_delete: Not connected to database!\n");
752 +       }
753 +}
754 +
755 +void db_log_error(enum logcode code, int errcode, const char *format,...)
756 +{
757 +       char strSqlStatement[MAXPATHLEN+1024];
758 +       va_list ap;
759 +       char buf[MAXPATHLEN+512];
760 +       size_t len;
761 +       SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
762 +       SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
763 +
764 +       if (!lp_database_logging(module_id))
765 +               return;
766 +
767 +       va_start(ap, format);
768 +       len = vsnprintf(buf, sizeof buf, format, ap);
769 +       va_end(ap);
770 +
771 +       /* Deal with buffer overruns.  Instead of panicking, just
772 +        * truncate the resulting string.  (Note that configure ensures
773 +        * that we have a vsnprintf() that doesn't ever return -1.) */
774 +       if (len > sizeof buf - 1) {
775 +               const char ellipsis[] = "[...]";
776 +
777 +               /* Reset length, and zero-terminate the end of our buffer */
778 +               len = sizeof buf - 1;
779 +               buf[len] = '\0';
780 +
781 +               /* Copy the ellipsis to the end of the string, but give
782 +                * us one extra character:
783 +                *
784 +                *                  v--- null byte at buf[sizeof buf - 1]
785 +                *        abcdefghij0
786 +                *     -> abcd[...]00  <-- now two null bytes at end
787 +                *
788 +                * If the input format string has a trailing newline,
789 +                * we copy it into that extra null; if it doesn't, well,
790 +                * all we lose is one byte.  */
791 +               strncpy(buf+len-sizeof ellipsis, ellipsis, sizeof ellipsis);
792 +               if (format[strlen(format)-1] == '\n') {
793 +                       buf[len-1] = '\n';
794 +               }
795 +       }
796 +
797 +       if (db_handle != NULL) {
798 +               snprintf(strSqlStatement, sizeof strSqlStatement,
799 +                   "INSERT INTO %s (session_id, date, logcode, error_number, error_text) VALUES ('%ld','%s','%d','%d','%s');",
800 +                   lp_error_table_name(module_id), session_id, timestring(time(NULL)), code, errcode, sanitizeSql(buf));
801 +
802 +               result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
803 +
804 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
805 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
806 +                       rprintf(FERROR,"Error at db_log_error: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
807 +               }
808 +       } else {
809 +               rprintf(FERROR,"Error at db_log_error: Not connected to database!\n");
810 +       }
811 +}
812 --- old/instructions
813 +++ new/instructions
814 @@ -0,0 +1,84 @@
815 +This patch adds the following options:
816 +
817 +"database logging"
818 +    If set to True, rsync will attempt to connect to
819 +    the specified datasource and write to the named tables.
820 +    Defaults to False.
821 +
822 +"database datasource"
823 +    Specifies the name of the ODBC data source to use.
824 +
825 +"database username"
826 +    The username to use when connecting to the database.
827 +
828 +"database password"
829 +    The password to use when connecting to the database.
830 +
831 +"transfer table name"
832 +    The name of the transfer table to log to.  This table contains individual
833 +    filenames, file sizes, bytes transferred, checksum bytes transferred,
834 +    operation (send or receive), and a timestamp.
835 +
836 +"session table name"
837 +    The name of the session table to log to.  This table contains the username,
838 +    module name, module path, ip address, process ID, and a timestamp.
839 +
840 +"exit table name"
841 +
842 +    The name of the exit table to log to.  This table contains the total bytes
843 +    read, total bytes written, total size of all files, error code, line the
844 +    error occured at, file the error occured at and the text of the error.
845 +    (most of which will be blank if the program exited normally).
846 +
847 +"delete table name"
848 +
849 +    The name of the table to log deleted files/directories to.
850 +
851 +"error table name"
852 +
853 +   The name of the table errors will be logged to.
854 +
855 +"unique id method"
856 +    Different databases use different methods to get a unique identifier.
857 +    Some databases support sequence objects, and use various forms of the
858 +    nextval command to retrieve a unique identifier from it.  Other databases
859 +    support an autonumber field, and support different methds of retrieving
860 +    the ID used in the last insert.  Valid values for this option are:
861 +
862 +       nextval-postgres
863 +           uses the syntax of nextval for PostgreSQL databases
864 +
865 +       nextval-oracle
866 +           uses the syntax of nextval for Oracle databases
867 +
868 +       nextval-db2
869 +           uses the syntax of nextval for DB2 databases
870 +
871 +       last_insert_id
872 +           uses the last_insert_id() command for the MySQL databases
873 +
874 +       @@IDENTITY
875 +           uses the @@IDENTITY command for Sybase databases
876 +
877 +       custom
878 +           Define your own method to get a unique identifier.  See the
879 +           "custom unique id select", and the "get custom id before select"
880 +           parameters.
881 +
882 +"sequence name"
883 +    If your database supports sequences, list the name of the sequence to use
884 +    for the session unique identifier.
885 +
886 +"custom unique id select"
887 +    Only used if you specify the custom method in "unique id method".  This is
888 +    a SQL statement to be executed to get a unique ID.  This SQL statement must
889 +    return one column with the unique ID to use for the session ID.  Should be
890 +    used in concert with the "get custom id before select" parameter.
891 +
892 +"get custom id before insert"
893 +    This parameter is ignored unless the "unique id method" is set to custom.
894 +    If set to true, the "custom unique id select" statement will be executed
895 +    BEFORE the session row is inserted into the database.  (as is done when a
896 +    sequence is used for unique IDs).  If False the statement will be executed
897 +    after the session row is inserted (as is done when the session ID is
898 +    automatically generates unique IDs).  Defaults to True.
899 --- old/loadparm.c
900 +++ new/loadparm.c
901 @@ -121,9 +121,16 @@ typedef struct
902  {
903         char *auth_users;
904         char *comment;
905 +       char *custom_unique_id_select;
906 +       char *database_datasource;
907 +       char *database_password;
908 +       char *database_username;
909 +       char *delete_table_name;
910         char *dont_compress;
911 +       char *error_table_name;
912         char *exclude;
913         char *exclude_from;
914 +       char *exit_table_name;
915         char *filter;
916         char *gid;
917         char *hosts_allow;
918 @@ -141,15 +148,21 @@ typedef struct
919         char *prexfer_exec;
920         char *refuse_options;
921         char *secrets_file;
922 +       char *sequence_name;
923 +       char *session_table_name;
924         char *temp_dir;
925 +       char *transfer_table_name;
926         char *uid;
927 +       char *unique_id_method;
928  
929         int max_connections;
930         int max_verbosity;
931         int syslog_facility;
932         int timeout;
933  
934 +       BOOL database_logging;
935         BOOL fake_super;
936 +       BOOL get_custom_id_before_insert;
937         BOOL ignore_errors;
938         BOOL ignore_nonreadable;
939         BOOL list;
940 @@ -169,9 +182,16 @@ static service sDefault =
941  {
942   /* auth_users; */             NULL,
943   /* comment; */                        NULL,
944 + /* custom_unique_id_select; */        NULL,
945 + /* database_datasource; */    NULL,
946 + /* database_password; */      NULL,
947 + /* database_username; */      NULL,
948 + /* delete_table_name; */      NULL,
949   /* dont_compress; */          "*.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz",
950 + /* error_table_name; */       NULL,
951   /* exclude; */                        NULL,
952   /* exclude_from; */           NULL,
953 + /* exit_table_name; */                NULL,
954   /* filter; */                 NULL,
955   /* gid; */                    NOBODY_GROUP,
956   /* hosts_allow; */            NULL,
957 @@ -189,15 +209,21 @@ static service sDefault =
958   /* prexfer_exec; */           NULL,
959   /* refuse_options; */         NULL,
960   /* secrets_file; */           NULL,
961 + /* sequence_name; */          NULL,
962 + /* session_table_name; */     NULL,
963   /* temp_dir; */               NULL,
964 + /* transfer_table_name; */    NULL,
965   /* uid; */                    NOBODY_USER,
966 + /* unique_id_method; */       NULL,
967  
968   /* max_connections; */                0,
969   /* max_verbosity; */          1,
970   /* syslog_facility; */                LOG_DAEMON,
971   /* timeout; */                        0,
972  
973 + /* database_logging; */       False,
974   /* fake_super; */             False,
975 + /* get_custom_id_before_insert; */ True,
976   /* ignore_errors; */          False,
977   /* ignore_nonreadable; */     False,
978   /* list; */                   True,
979 @@ -296,11 +322,20 @@ static struct parm_struct parm_table[] =
980  
981   {"auth users",        P_STRING, P_LOCAL, &sDefault.auth_users,        NULL,0},
982   {"comment",           P_STRING, P_LOCAL, &sDefault.comment,           NULL,0},
983 + {"custom unique id select",P_STRING,P_LOCAL,&sDefault.custom_unique_id_select,NULL,0},
984 + {"database datasource",P_STRING,P_LOCAL, &sDefault.database_datasource,NULL,0},
985 + {"database logging",  P_BOOL,   P_LOCAL, &sDefault.database_logging,  NULL,0},
986 + {"database password", P_STRING, P_LOCAL, &sDefault.database_password, NULL,0},
987 + {"database username", P_STRING, P_LOCAL, &sDefault.database_username, NULL,0},
988 + {"delete table name", P_STRING, P_LOCAL, &sDefault.delete_table_name, NULL,0},
989   {"dont compress",     P_STRING, P_LOCAL, &sDefault.dont_compress,     NULL,0},
990 + {"error table name",  P_STRING, P_LOCAL, &sDefault.error_table_name,  NULL,0},
991   {"exclude from",      P_STRING, P_LOCAL, &sDefault.exclude_from,      NULL,0},
992   {"exclude",           P_STRING, P_LOCAL, &sDefault.exclude,           NULL,0},
993 + {"exit table name",   P_STRING, P_LOCAL, &sDefault.exit_table_name,   NULL,0},
994   {"fake super",        P_BOOL,   P_LOCAL, &sDefault.fake_super,        NULL,0},
995   {"filter",            P_STRING, P_LOCAL, &sDefault.filter,            NULL,0},
996 + {"get custom id before insert",P_BOOL,P_LOCAL,&sDefault.get_custom_id_before_insert,NULL,0},
997   {"gid",               P_STRING, P_LOCAL, &sDefault.gid,               NULL,0},
998   {"hosts allow",       P_STRING, P_LOCAL, &sDefault.hosts_allow,       NULL,0},
999   {"hosts deny",        P_STRING, P_LOCAL, &sDefault.hosts_deny,        NULL,0},
1000 @@ -325,12 +360,16 @@ static struct parm_struct parm_table[] =
1001   {"read only",         P_BOOL,   P_LOCAL, &sDefault.read_only,         NULL,0},
1002   {"refuse options",    P_STRING, P_LOCAL, &sDefault.refuse_options,    NULL,0},
1003   {"secrets file",      P_STRING, P_LOCAL, &sDefault.secrets_file,      NULL,0},
1004 + {"sequence name",     P_STRING, P_LOCAL, &sDefault.sequence_name,     NULL,0},
1005 + {"session table name",P_STRING, P_LOCAL, &sDefault.session_table_name,NULL,0},
1006   {"strict modes",      P_BOOL,   P_LOCAL, &sDefault.strict_modes,      NULL,0},
1007   {"syslog facility",   P_ENUM,   P_LOCAL, &sDefault.syslog_facility,enum_facilities,0},
1008   {"temp dir",          P_PATH,   P_LOCAL, &sDefault.temp_dir,          NULL,0},
1009   {"timeout",           P_INTEGER,P_LOCAL, &sDefault.timeout,           NULL,0},
1010   {"transfer logging",  P_BOOL,   P_LOCAL, &sDefault.transfer_logging,  NULL,0},
1011 + {"transfer table name",P_STRING,P_LOCAL, &sDefault.transfer_table_name,NULL,0},
1012   {"uid",               P_STRING, P_LOCAL, &sDefault.uid,               NULL,0},
1013 + {"unique id method",  P_STRING, P_LOCAL, &sDefault.unique_id_method,  NULL,0},
1014   {"use chroot",        P_BOOL,   P_LOCAL, &sDefault.use_chroot,        NULL,0},
1015   {"write only",        P_BOOL,   P_LOCAL, &sDefault.write_only,        NULL,0},
1016   {NULL,                P_BOOL,   P_NONE,  NULL,                        NULL,0}
1017 @@ -386,9 +425,16 @@ FN_GLOBAL_INTEGER(lp_rsync_port, &Global
1018  
1019  FN_LOCAL_STRING(lp_auth_users, auth_users)
1020  FN_LOCAL_STRING(lp_comment, comment)
1021 +FN_LOCAL_STRING(lp_custom_unique_id_select,custom_unique_id_select)
1022 +FN_LOCAL_STRING(lp_database_datasource, database_datasource)
1023 +FN_LOCAL_STRING(lp_database_password, database_password)
1024 +FN_LOCAL_STRING(lp_database_username, database_username)
1025 +FN_LOCAL_STRING(lp_delete_table_name,delete_table_name)
1026  FN_LOCAL_STRING(lp_dont_compress, dont_compress)
1027 +FN_LOCAL_STRING(lp_error_table_name,error_table_name)
1028  FN_LOCAL_STRING(lp_exclude, exclude)
1029  FN_LOCAL_STRING(lp_exclude_from, exclude_from)
1030 +FN_LOCAL_STRING(lp_exit_table_name, exit_table_name)
1031  FN_LOCAL_STRING(lp_filter, filter)
1032  FN_LOCAL_STRING(lp_gid, gid)
1033  FN_LOCAL_STRING(lp_hosts_allow, hosts_allow)
1034 @@ -406,15 +452,21 @@ FN_LOCAL_STRING(lp_postxfer_exec, postxf
1035  FN_LOCAL_STRING(lp_prexfer_exec, prexfer_exec)
1036  FN_LOCAL_STRING(lp_refuse_options, refuse_options)
1037  FN_LOCAL_STRING(lp_secrets_file, secrets_file)
1038 +FN_LOCAL_STRING(lp_sequence_name,sequence_name)
1039 +FN_LOCAL_STRING(lp_session_table_name,session_table_name)
1040  FN_LOCAL_INTEGER(lp_syslog_facility, syslog_facility)
1041  FN_LOCAL_STRING(lp_temp_dir, temp_dir)
1042 +FN_LOCAL_STRING(lp_transfer_table_name, transfer_table_name)
1043  FN_LOCAL_STRING(lp_uid, uid)
1044 +FN_LOCAL_STRING(lp_unique_id_method,unique_id_method)
1045  
1046  FN_LOCAL_INTEGER(lp_max_connections, max_connections)
1047  FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity)
1048  FN_LOCAL_INTEGER(lp_timeout, timeout)
1049  
1050 +FN_LOCAL_BOOL(lp_database_logging, database_logging)
1051  FN_LOCAL_BOOL(lp_fake_super, fake_super)
1052 +FN_LOCAL_BOOL(lp_get_custom_id_before_insert,get_custom_id_before_insert)
1053  FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
1054  FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
1055  FN_LOCAL_BOOL(lp_list, list)
1056 --- old/log.c
1057 +++ new/log.c
1058 @@ -94,7 +94,7 @@ struct {
1059  /*
1060   * Map from rsync error code to name, or return NULL.
1061   */
1062 -static char const *rerr_name(int code)
1063 +char const *rerr_name(int code)
1064  {
1065         int i;
1066         for (i = 0; rerr_names[i].name; i++) {
1067 --- old/receiver.c
1068 +++ new/receiver.c
1069 @@ -111,6 +111,10 @@ int get_tmpname(char *fnametmp, char *fn
1070  
1071         if (maxname < 1) {
1072                 rprintf(FERROR, "temporary filename too long: %s\n", fname);
1073 +#ifdef HAVE_LIBODBC
1074 +               db_log_error(FERROR,13, "temporary filename too long: %s\n",
1075 +                       fname);
1076 +#endif
1077                 fnametmp[0] = '\0';
1078                 return 0;
1079         }
1080 @@ -174,6 +178,10 @@ static int receive_data(int f_in, char *
1081                 if (fd != -1 && (j = do_lseek(fd, offset, SEEK_SET)) != offset) {
1082                         rsyserr(FERROR, errno, "lseek of %s returned %.0f, not %.0f",
1083                                 full_fname(fname), (double)j, (double)offset);
1084 +#ifdef HAVE_LIBODBC
1085 +                       db_log_error(FERROR, 14, "lseek failed on %s",
1086 +                               full_fname(fname));
1087 +#endif
1088                         exit_cleanup(RERR_FILEIO);
1089                 }
1090         }
1091 @@ -231,6 +239,11 @@ static int receive_data(int f_in, char *
1092                                                 "lseek of %s returned %.0f, not %.0f",
1093                                                 full_fname(fname),
1094                                                 (double)pos, (double)offset);
1095 +#ifdef HAVE_LIBODBC
1096 +                                       db_log_error(FERROR, 14,
1097 +                                               "lseek failed on %s",
1098 +                                               full_fname(fname));
1099 +#endif
1100                                         exit_cleanup(RERR_FILEIO);
1101                                 }
1102                                 continue;
1103 @@ -256,6 +269,9 @@ static int receive_data(int f_in, char *
1104             report_write_error:
1105                 rsyserr(FERROR, errno, "write failed on %s",
1106                         full_fname(fname));
1107 +#ifdef HAVE_LIBODBC
1108 +               db_log_error(FERROR, 15, "write failed on %s",full_fname(fname));
1109 +#endif
1110                 exit_cleanup(RERR_FILEIO);
1111         }
1112  
1113 @@ -299,6 +315,12 @@ static void handle_delayed_updates(char 
1114                                 rsyserr(FERROR, errno,
1115                                         "rename failed for %s (from %s)",
1116                                         full_fname(fname), partialptr);
1117 +#ifdef HAVE_LIBODBC
1118 +                               db_log_error(FERROR, 16,
1119 +                                       "rename failed for %s (from %s)",
1120 +                                       full_fname(fname),
1121 +                                       partialptr);
1122 +#endif
1123                         } else {
1124                                 if (remove_source_files
1125                                  || (preserve_hard_links && F_IS_HLINKED(file)))
1126 @@ -447,6 +469,9 @@ int recv_files(int f_in, char *local_nam
1127                 if (server_filter_list.head
1128                     && check_filter(&server_filter_list, fname, 0) < 0) {
1129                         rprintf(FERROR, "attempt to hack rsync failed.\n");
1130 +#ifdef HAVE_LIBODBC
1131 +                       db_log_error(FERROR,17,"attempt to hack rsync failed.");
1132 +#endif
1133                         exit_cleanup(RERR_PROTOCOL);
1134                 }
1135  
1136 @@ -503,6 +528,11 @@ int recv_files(int f_in, char *local_nam
1137                                         rprintf(FERROR,
1138                                                 "invalid basis_dir index: %d.\n",
1139                                                 fnamecmp_type);
1140 +#ifdef HAVE_LIBODBC
1141 +                                       db_log_error(FERROR, 18,
1142 +                                               "invalid basis_dir index: %d.\n",
1143 +                                               fnamecmp_type);
1144 +#endif
1145                                         exit_cleanup(RERR_PROTOCOL);
1146                                 }
1147                                 pathjoin(fnamecmpbuf, sizeof fnamecmpbuf,
1148 @@ -551,6 +581,9 @@ int recv_files(int f_in, char *local_nam
1149                 } else if (do_fstat(fd1,&st) != 0) {
1150                         rsyserr(FERROR, errno, "fstat %s failed",
1151                                 full_fname(fnamecmp));
1152 +#ifdef HAVE_LIBODBC
1153 +                       db_log_error(FERROR, 19,"fstat %s failed",full_fname(fnamecmp));
1154 +#endif
1155                         discard_receive_data(f_in, F_LENGTH(file));
1156                         close(fd1);
1157                         continue;
1158 @@ -564,6 +597,9 @@ int recv_files(int f_in, char *local_nam
1159                          */
1160                         rprintf(FERROR,"recv_files: %s is a directory\n",
1161                                 full_fname(fnamecmp));
1162 +#ifdef HAVE_LIBODBC
1163 +                       db_log_error(FERROR,20,"recv_files: %s is a directory",full_fname(fnamecmp));
1164 +#endif
1165                         discard_receive_data(f_in, F_LENGTH(file));
1166                         close(fd1);
1167                         continue;
1168 @@ -596,6 +632,9 @@ int recv_files(int f_in, char *local_nam
1169                         if (fd2 == -1) {
1170                                 rsyserr(FERROR, errno, "open %s failed",
1171                                         full_fname(fname));
1172 +#ifdef HAVE_LIBODBC
1173 +                               db_log_error(FERROR,22, "open %s failed", full_fname(fname));
1174 +#endif
1175                                 discard_receive_data(f_in, F_LENGTH(file));
1176                                 if (fd1 != -1)
1177                                         close(fd1);
1178 @@ -629,6 +668,10 @@ int recv_files(int f_in, char *local_nam
1179                         if (fd2 == -1) {
1180                                 rsyserr(FERROR, errno, "mkstemp %s failed",
1181                                         full_fname(fnametmp));
1182 +#ifdef HAVE_LIBODBC
1183 +                               db_log_error(FERROR, 22, "mkstemp %s failed",
1184 +                                       full_fname(fnametmp));
1185 +#endif
1186                                 discard_receive_data(f_in, F_LENGTH(file));
1187                                 if (fd1 != -1)
1188                                         close(fd1);
1189 @@ -649,12 +692,19 @@ int recv_files(int f_in, char *local_nam
1190                                        fname, fd2, F_LENGTH(file));
1191  
1192                 log_item(log_code, file, &initial_stats, iflags, NULL);
1193 +#ifdef HAVE_LIBODBC
1194 +               db_log_transfer(file, &initial_stats, "receive");
1195 +#endif
1196  
1197                 if (fd1 != -1)
1198                         close(fd1);
1199                 if (close(fd2) < 0) {
1200                         rsyserr(FERROR, errno, "close failed on %s",
1201                                 full_fname(fnametmp));
1202 +#ifdef HAVE_LIBODBC
1203 +                       db_log_error(FERROR, 23, "close failed on %s",
1204 +                               full_fname(fnametmp));
1205 +#endif
1206                         exit_cleanup(RERR_FILEIO);
1207                 }
1208  
1209 @@ -711,6 +761,12 @@ int recv_files(int f_in, char *local_nam
1210                                 rprintf(msgtype,
1211                                         "%s: %s failed verification -- update %s%s.\n",
1212                                         errstr, fname, keptstr, redostr);
1213 +#ifdef HAVE_LIBODBC
1214 +                               db_log_error(msgtype,24,
1215 +                                       "%s: %s failed verification -- update %s%s.\n",
1216 +                                       errstr, fname,
1217 +                                       keptstr, redostr);
1218 +#endif
1219                         }
1220                         if (!redoing) {
1221                                 send_msg_int(MSG_REDO, ndx);
1222 --- old/sender.c
1223 +++ new/sender.c
1224 @@ -341,6 +341,9 @@ void send_files(int f_in, int f_out)
1225                         end_progress(st.st_size);
1226  
1227                 log_item(log_code, file, &initial_stats, iflags, NULL);
1228 +#ifdef HAVE_LIBODBC
1229 +               db_log_transfer(file, &initial_stats,"send");
1230 +#endif
1231  
1232                 if (mbuf) {
1233                         j = unmap_file(mbuf);