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