1 Add support for logging daemon messages to an SQL database.
3 To use this patch, run these commands for a successful build:
5 patch -p1 <patches/ODBC-dblog.diff
7 ./configure --enable-ODBC
10 See the newly-created file "instructions" for more info.
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
22 OBJS3=progress.o pipe.o
25 @@ -27,6 +27,7 @@ extern int am_server;
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 @@ -174,8 +175,13 @@ NORETURN void _exit_cleanup(int code, co
34 code = exit_code = RERR_PARTIAL;
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);
41 + db_log_exit(code, file, line);
48 --- old/clientserver.c
49 +++ new/clientserver.c
50 @@ -397,6 +397,9 @@ static int rsync_module(int f_in, int f_
51 XFLG_ABS_IF_SLASH | XFLG_OLD_PREFIXES);
59 if (*lp_prexfer_exec(i) || *lp_postxfer_exec(i)) {
60 @@ -638,6 +641,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);
68 rprintf(FLOG, "rsync %s %s from %s (%s)\n",
69 am_sender ? "on" : "to",
72 @@ -637,6 +637,12 @@ if test x"$with_included_popt" != x"yes"
73 AC_CHECK_LIB(popt, poptGetContext, , [with_included_popt=yes])
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) ])
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
88 +error type description
91 +2 file/dir deletion failed
94 +5 multiplexing overflow
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
106 +17 rsync hack failed
107 +18 "invalid basis_dir index
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
121 +32 failed to open file/directory
123 --- old/dblog-tables-mysql.sql
124 +++ new/dblog-tables-mysql.sql
126 +drop table transfer;
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,
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),
151 + foreign key (session_id) references session (id)
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,
167 + foreign key (session_id) references session (id)
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),
178 + foreign key (session_id) references session (id)
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,
188 + foreign key (session_id) references session (id)
190 --- old/dblog-tables-postgresql.sql
191 +++ new/dblog-tables-postgresql.sql
193 +drop table transfer;
196 +drop sequence session_id_seq;
197 +create sequence session_id_seq;
199 +CREATE TABLE "session" (
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,
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),
220 + foreign key (session_id) references session (id)
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,
236 + foreign key (session_id) references session (id)
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),
247 + foreign key (session_id) references session (id)
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,
258 + foreign key (session_id) references session (id)
264 + * ODBC Database logging functions
266 + * Written by Steve Sether, April 2004
267 + * steve@vellmont.com
275 +#ifdef HAVE_ODBC_SQL_H
276 +#include <odbc/sql.h>
280 +#ifdef HAVE_SQLEXT_H
283 +#ifdef HAVE_ODBC_SQLEXT_H
284 +#include <odbc/sqlext.h>
288 +#ifdef HAVE_SQLTYPES_H
289 +#include <sqltypes.h>
291 +#ifdef HAVE_ODBC_SQLTYPES_H
292 +#include <odbc/sqltypes.h>
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;
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;
317 +/* This function simply removes invalid characters from the SQL statement
318 + * to prevent SQL injection attacks. */
319 +char *sanitizeSql(const char *input)
324 + if (strlen(input) > ((~(unsigned int)0)>>1)-3)
326 + if (!(out = ptr = new_array(char, strlen(input) * 2 + 1)))
329 + for (c = input; *c; c++) {
360 +void db_log_open(void)
362 + if (!lp_database_logging(module_id))
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");
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;
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;
390 + /* Set connection attributes for the generator db connection */
391 + SQLSetConnectAttr(db_handle_g, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
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);
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;
408 + rprintf(FLOG,"Connected to database for generator!\n");
410 + rprintf(FERROR,"Already connected to database for generator\n");
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;
422 + /* Set connection attributes for the receiver db connection */
423 + SQLSetConnectAttr(db_handle_r, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
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);
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;
440 + rprintf(FLOG,"Connected to database for receiver!\n");
442 + rprintf(FERROR,"Already connected to database for receiver\n");
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;
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;
474 + if (!lp_database_logging(module_id))
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;
483 + rprintf(FERROR,"No generator sql statement handle to close\n");
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;
492 + rprintf(FERROR,"Generator database connection already closed\n");
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;
500 + rprintf(FERROR,"No receiver sql statement handle to close\n");
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;
509 + rprintf(FERROR,"Receiver database connection already closed\n");
513 + if (db_environ_handle != NULL) {
514 + /* free the environment handle */
515 + SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
516 + db_environ_handle = NULL;
518 + rprintf(FERROR,"No environment handle to close\n");
522 +static long get_unique_session_id()
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;
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));
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);
562 + result = SQLFetch(sql_statement_handle);
563 + if (result != SQL_NO_DATA && unique != 0) {
564 + rprintf(FINFO,"Got unique sequence! %ld\n",unique);
566 + rprintf(FERROR,"Error at get_sequence: Didn't get unique session ID\n");
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);
578 + rprintf(FERROR,"Error at get_sequence: Not connected to database\n");
582 +void db_log_session()
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;
589 + if (!lp_database_logging(module_id))
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();
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());
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());
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);
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);
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();
634 + rprintf(FERROR,"Error at db_log_session: Not connected to database!\n");
638 +void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation)
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;
650 + if (!lp_database_logging(module_id))
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;
660 + clean_fname(strFileNamePtr, 0);
661 + if (*strFileNamePtr == '/')
664 + snprintf(strFileSize, sizeof strFileSize, "%.0f", (double)F_LENGTH(file));
666 + intBytesTransferred = stats.total_written - initial_stats->total_written;
668 + intBytesTransferred = stats.total_read - initial_stats->total_read;
672 + intCheckSumBytes = stats.total_written - initial_stats->total_written;
674 + intCheckSumBytes = stats.total_read - initial_stats->total_read;
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");
690 + rprintf(FERROR,"Error at db_log_transfer: Not connected to database!\n");
694 +void db_log_exit(int code, const char *file, int line)
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;
702 + if (!lp_database_logging(module_id))
705 + if (db_handle != NULL) {
707 + error_text = rerr_name(code);
709 + error_text = "unexplained error";
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());
719 + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
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);
726 + rprintf(FERROR,"Error at db_log_exit: Not connected to database!\n");
730 +void db_log_delete(char *fname, int mode)
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;
736 + if (!am_daemon || dry_run || !lp_database_logging(module_id))
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);
744 + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
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);
751 + rprintf(FERROR,"Error at db_log_delete: Not connected to database!\n");
755 +void db_log_error(enum logcode code, int errcode, const char *format,...)
757 + char strSqlStatement[MAXPATHLEN+1024];
759 + char buf[MAXPATHLEN+512];
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;
764 + if (!lp_database_logging(module_id))
767 + va_start(ap, format);
768 + len = vsnprintf(buf, sizeof buf, format, ap);
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[] = "[...]";
777 + /* Reset length, and zero-terminate the end of our buffer */
778 + len = sizeof buf - 1;
781 + /* Copy the ellipsis to the end of the string, but give
782 + * us one extra character:
784 + * v--- null byte at buf[sizeof buf - 1]
786 + * -> abcd[...]00 <-- now two null bytes at end
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') {
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));
802 + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
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);
809 + rprintf(FERROR,"Error at db_log_error: Not connected to database!\n");
815 +This patch adds the following options:
818 + If set to True, rsync will attempt to connect to
819 + the specified datasource and write to the named tables.
822 +"database datasource"
823 + Specifies the name of the ODBC data source to use.
826 + The username to use when connecting to the database.
829 + The password to use when connecting to the database.
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.
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.
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).
849 + The name of the table to log deleted files/directories to.
853 + The name of the table errors will be logged to.
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:
863 + uses the syntax of nextval for PostgreSQL databases
866 + uses the syntax of nextval for Oracle databases
869 + uses the syntax of nextval for DB2 databases
872 + uses the last_insert_id() command for the MySQL databases
875 + uses the @@IDENTITY command for Sybase databases
878 + Define your own method to get a unique identifier. See the
879 + "custom unique id select", and the "get custom id before select"
883 + If your database supports sequences, list the name of the sequence to use
884 + for the session unique identifier.
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.
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.
901 @@ -122,9 +122,16 @@ typedef struct
905 + char *custom_unique_id_select;
906 + char *database_datasource;
907 + char *database_password;
908 + char *database_username;
909 + char *delete_table_name;
911 + char *error_table_name;
914 + char *exit_table_name;
918 @@ -142,14 +149,20 @@ typedef struct
920 char *refuse_options;
922 + char *sequence_name;
923 + char *session_table_name;
925 + char *transfer_table_name;
927 + char *unique_id_method;
934 + BOOL database_logging;
935 + BOOL get_custom_id_before_insert;
937 BOOL ignore_nonreadable;
939 @@ -169,9 +182,16 @@ static service sDefault =
941 /* auth_users; */ NULL,
943 + /* custom_unique_id_select; */ NULL,
944 + /* database_datasource; */ NULL,
945 + /* database_password; */ NULL,
946 + /* database_username; */ NULL,
947 + /* delete_table_name; */ NULL,
948 /* dont_compress; */ "*.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz",
949 + /* error_table_name; */ NULL,
951 /* exclude_from; */ NULL,
952 + /* exit_table_name; */ NULL,
954 /* gid; */ NOBODY_GROUP,
955 /* hosts_allow; */ NULL,
956 @@ -189,14 +209,20 @@ static service sDefault =
957 /* prexfer_exec; */ NULL,
958 /* refuse_options; */ NULL,
959 /* secrets_file; */ NULL,
960 + /* sequence_name; */ NULL,
961 + /* session_table_name; */ NULL,
962 /* temp_dir; */ NULL,
963 + /* transfer_table_name; */ NULL,
964 /* uid; */ NOBODY_USER,
965 + /* unique_id_method; */ NULL,
967 /* max_connections; */ 0,
968 /* max_verbosity; */ 1,
969 /* syslog_facility; */ LOG_DAEMON,
972 + /* database_logging; */ False,
973 + /* get_custom_id_before_insert; */ True,
974 /* ignore_errors; */ False,
975 /* ignore_nonreadable; */ False,
977 @@ -295,10 +321,19 @@ static struct parm_struct parm_table[] =
979 {"auth users", P_STRING, P_LOCAL, &sDefault.auth_users, NULL,0},
980 {"comment", P_STRING, P_LOCAL, &sDefault.comment, NULL,0},
981 + {"custom unique id select",P_STRING,P_LOCAL,&sDefault.custom_unique_id_select,NULL,0},
982 + {"database datasource",P_STRING,P_LOCAL, &sDefault.database_datasource,NULL,0},
983 + {"database logging", P_BOOL, P_LOCAL, &sDefault.database_logging, NULL,0},
984 + {"database password", P_STRING, P_LOCAL, &sDefault.database_password, NULL,0},
985 + {"database username", P_STRING, P_LOCAL, &sDefault.database_username, NULL,0},
986 + {"delete table name", P_STRING, P_LOCAL, &sDefault.delete_table_name, NULL,0},
987 {"dont compress", P_STRING, P_LOCAL, &sDefault.dont_compress, NULL,0},
988 + {"error table name", P_STRING, P_LOCAL, &sDefault.error_table_name, NULL,0},
989 {"exclude from", P_STRING, P_LOCAL, &sDefault.exclude_from, NULL,0},
990 {"exclude", P_STRING, P_LOCAL, &sDefault.exclude, NULL,0},
991 + {"exit table name", P_STRING, P_LOCAL, &sDefault.exit_table_name, NULL,0},
992 {"filter", P_STRING, P_LOCAL, &sDefault.filter, NULL,0},
993 + {"get custom id before insert",P_BOOL,P_LOCAL,&sDefault.get_custom_id_before_insert,NULL,0},
994 {"gid", P_STRING, P_LOCAL, &sDefault.gid, NULL,0},
995 {"hosts allow", P_STRING, P_LOCAL, &sDefault.hosts_allow, NULL,0},
996 {"hosts deny", P_STRING, P_LOCAL, &sDefault.hosts_deny, NULL,0},
997 @@ -323,12 +358,16 @@ static struct parm_struct parm_table[] =
998 {"read only", P_BOOL, P_LOCAL, &sDefault.read_only, NULL,0},
999 {"refuse options", P_STRING, P_LOCAL, &sDefault.refuse_options, NULL,0},
1000 {"secrets file", P_STRING, P_LOCAL, &sDefault.secrets_file, NULL,0},
1001 + {"sequence name", P_STRING, P_LOCAL, &sDefault.sequence_name, NULL,0},
1002 + {"session table name",P_STRING, P_LOCAL, &sDefault.session_table_name,NULL,0},
1003 {"strict modes", P_BOOL, P_LOCAL, &sDefault.strict_modes, NULL,0},
1004 {"syslog facility", P_ENUM, P_LOCAL, &sDefault.syslog_facility,enum_facilities,0},
1005 {"temp dir", P_PATH, P_LOCAL, &sDefault.temp_dir, NULL,0},
1006 {"timeout", P_INTEGER,P_LOCAL, &sDefault.timeout, NULL,0},
1007 {"transfer logging", P_BOOL, P_LOCAL, &sDefault.transfer_logging, NULL,0},
1008 + {"transfer table name",P_STRING,P_LOCAL, &sDefault.transfer_table_name,NULL,0},
1009 {"uid", P_STRING, P_LOCAL, &sDefault.uid, NULL,0},
1010 + {"unique id method", P_STRING, P_LOCAL, &sDefault.unique_id_method, NULL,0},
1011 {"use chroot", P_BOOL, P_LOCAL, &sDefault.use_chroot, NULL,0},
1012 {"write only", P_BOOL, P_LOCAL, &sDefault.write_only, NULL,0},
1013 {NULL, P_BOOL, P_NONE, NULL, NULL,0}
1014 @@ -384,9 +423,16 @@ FN_GLOBAL_INTEGER(lp_rsync_port, &Global
1016 FN_LOCAL_STRING(lp_auth_users, auth_users)
1017 FN_LOCAL_STRING(lp_comment, comment)
1018 +FN_LOCAL_STRING(lp_custom_unique_id_select,custom_unique_id_select)
1019 +FN_LOCAL_STRING(lp_database_datasource, database_datasource)
1020 +FN_LOCAL_STRING(lp_database_password, database_password)
1021 +FN_LOCAL_STRING(lp_database_username, database_username)
1022 +FN_LOCAL_STRING(lp_delete_table_name,delete_table_name)
1023 FN_LOCAL_STRING(lp_dont_compress, dont_compress)
1024 +FN_LOCAL_STRING(lp_error_table_name,error_table_name)
1025 FN_LOCAL_STRING(lp_exclude, exclude)
1026 FN_LOCAL_STRING(lp_exclude_from, exclude_from)
1027 +FN_LOCAL_STRING(lp_exit_table_name, exit_table_name)
1028 FN_LOCAL_STRING(lp_filter, filter)
1029 FN_LOCAL_STRING(lp_gid, gid)
1030 FN_LOCAL_STRING(lp_hosts_allow, hosts_allow)
1031 @@ -404,14 +450,20 @@ FN_LOCAL_STRING(lp_postxfer_exec, postxf
1032 FN_LOCAL_STRING(lp_prexfer_exec, prexfer_exec)
1033 FN_LOCAL_STRING(lp_refuse_options, refuse_options)
1034 FN_LOCAL_STRING(lp_secrets_file, secrets_file)
1035 +FN_LOCAL_STRING(lp_sequence_name,sequence_name)
1036 +FN_LOCAL_STRING(lp_session_table_name,session_table_name)
1037 FN_LOCAL_INTEGER(lp_syslog_facility, syslog_facility)
1038 FN_LOCAL_STRING(lp_temp_dir, temp_dir)
1039 +FN_LOCAL_STRING(lp_transfer_table_name, transfer_table_name)
1040 FN_LOCAL_STRING(lp_uid, uid)
1041 +FN_LOCAL_STRING(lp_unique_id_method,unique_id_method)
1043 FN_LOCAL_INTEGER(lp_max_connections, max_connections)
1044 FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity)
1045 FN_LOCAL_INTEGER(lp_timeout, timeout)
1047 +FN_LOCAL_BOOL(lp_database_logging, database_logging)
1048 +FN_LOCAL_BOOL(lp_get_custom_id_before_insert,get_custom_id_before_insert)
1049 FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
1050 FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
1051 FN_LOCAL_BOOL(lp_list, list)
1054 @@ -95,7 +95,7 @@ struct {
1056 * Map from rsync error code to name, or return NULL.
1058 -static char const *rerr_name(int code)
1059 +char const *rerr_name(int code)
1062 for (i = 0; rerr_names[i].name; i++) {
1065 @@ -110,6 +110,10 @@ int get_tmpname(char *fnametmp, char *fn
1068 rprintf(FERROR, "temporary filename too long: %s\n", fname);
1069 +#ifdef HAVE_LIBODBC
1070 + db_log_error(FERROR,13, "temporary filename too long: %s\n",
1076 @@ -173,6 +177,10 @@ static int receive_data(int f_in, char *
1077 if (fd != -1 && (j = do_lseek(fd, offset, SEEK_SET)) != offset) {
1078 rsyserr(FERROR, errno, "lseek of %s returned %.0f, not %.0f",
1079 full_fname(fname), (double)j, (double)offset);
1080 +#ifdef HAVE_LIBODBC
1081 + db_log_error(FERROR, 14, "lseek failed on %s",
1082 + full_fname(fname));
1084 exit_cleanup(RERR_FILEIO);
1087 @@ -230,6 +238,11 @@ static int receive_data(int f_in, char *
1088 "lseek of %s returned %.0f, not %.0f",
1090 (double)pos, (double)offset);
1091 +#ifdef HAVE_LIBODBC
1092 + db_log_error(FERROR, 14,
1093 + "lseek failed on %s",
1094 + full_fname(fname));
1096 exit_cleanup(RERR_FILEIO);
1099 @@ -255,6 +268,9 @@ static int receive_data(int f_in, char *
1101 rsyserr(FERROR, errno, "write failed on %s",
1103 +#ifdef HAVE_LIBODBC
1104 + db_log_error(FERROR, 15, "write failed on %s",full_fname(fname));
1106 exit_cleanup(RERR_FILEIO);
1109 @@ -298,6 +314,12 @@ static void handle_delayed_updates(char
1110 rsyserr(FERROR, errno,
1111 "rename failed for %s (from %s)",
1112 full_fname(fname), partialptr);
1113 +#ifdef HAVE_LIBODBC
1114 + db_log_error(FERROR, 16,
1115 + "rename failed for %s (from %s)",
1116 + full_fname(fname),
1120 if (remove_source_files
1121 || (preserve_hard_links && F_IS_HLINKED(file)))
1122 @@ -431,6 +453,9 @@ int recv_files(int f_in, char *local_nam
1123 if (server_filter_list.head
1124 && check_filter(&server_filter_list, fname, 0) < 0) {
1125 rprintf(FERROR, "attempt to hack rsync failed.\n");
1126 +#ifdef HAVE_LIBODBC
1127 + db_log_error(FERROR,17,"attempt to hack rsync failed.");
1129 exit_cleanup(RERR_PROTOCOL);
1132 @@ -487,6 +512,11 @@ int recv_files(int f_in, char *local_nam
1134 "invalid basis_dir index: %d.\n",
1136 +#ifdef HAVE_LIBODBC
1137 + db_log_error(FERROR, 18,
1138 + "invalid basis_dir index: %d.\n",
1141 exit_cleanup(RERR_PROTOCOL);
1143 pathjoin(fnamecmpbuf, sizeof fnamecmpbuf,
1144 @@ -535,6 +565,9 @@ int recv_files(int f_in, char *local_nam
1145 } else if (do_fstat(fd1,&st) != 0) {
1146 rsyserr(FERROR, errno, "fstat %s failed",
1147 full_fname(fnamecmp));
1148 +#ifdef HAVE_LIBODBC
1149 + db_log_error(FERROR, 19,"fstat %s failed",full_fname(fnamecmp));
1151 discard_receive_data(f_in, F_LENGTH(file));
1154 @@ -548,6 +581,9 @@ int recv_files(int f_in, char *local_nam
1156 rprintf(FERROR,"recv_files: %s is a directory\n",
1157 full_fname(fnamecmp));
1158 +#ifdef HAVE_LIBODBC
1159 + db_log_error(FERROR,20,"recv_files: %s is a directory",full_fname(fnamecmp));
1161 discard_receive_data(f_in, F_LENGTH(file));
1164 @@ -571,6 +607,9 @@ int recv_files(int f_in, char *local_nam
1166 rsyserr(FERROR, errno, "open %s failed",
1168 +#ifdef HAVE_LIBODBC
1169 + db_log_error(FERROR,22, "open %s failed", full_fname(fname));
1171 discard_receive_data(f_in, F_LENGTH(file));
1174 @@ -604,6 +643,10 @@ int recv_files(int f_in, char *local_nam
1176 rsyserr(FERROR, errno, "mkstemp %s failed",
1177 full_fname(fnametmp));
1178 +#ifdef HAVE_LIBODBC
1179 + db_log_error(FERROR, 22, "mkstemp %s failed",
1180 + full_fname(fnametmp));
1182 discard_receive_data(f_in, F_LENGTH(file));
1185 @@ -624,12 +667,19 @@ int recv_files(int f_in, char *local_nam
1186 fname, fd2, F_LENGTH(file));
1188 log_item(log_code, file, &initial_stats, iflags, NULL);
1189 +#ifdef HAVE_LIBODBC
1190 + db_log_transfer(file, &initial_stats, "receive");
1195 if (close(fd2) < 0) {
1196 rsyserr(FERROR, errno, "close failed on %s",
1197 full_fname(fnametmp));
1198 +#ifdef HAVE_LIBODBC
1199 + db_log_error(FERROR, 23, "close failed on %s",
1200 + full_fname(fnametmp));
1202 exit_cleanup(RERR_FILEIO);
1205 @@ -686,6 +736,12 @@ int recv_files(int f_in, char *local_nam
1207 "%s: %s failed verification -- update %s%s.\n",
1208 errstr, fname, keptstr, redostr);
1209 +#ifdef HAVE_LIBODBC
1210 + db_log_error(msgtype,24,
1211 + "%s: %s failed verification -- update %s%s.\n",
1213 + keptstr, redostr);
1216 if (!phase || incremental) {
1217 send_msg_int(MSG_REDO, ndx);
1220 @@ -326,6 +326,9 @@ void send_files(int f_in, int f_out)
1221 end_progress(st.st_size);
1223 log_item(log_code, file, &initial_stats, iflags, NULL);
1224 +#ifdef HAVE_LIBODBC
1225 + db_log_transfer(file, &initial_stats,"send");
1229 j = unmap_file(mbuf);