1 Add support for logging daemon messages to an SQL database.
3 After applying this patch, run these commands for a successful build:
6 ./configure --enable-ODBC
9 See the file "instructions" (after applying this patch) for more info.
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
27 extern int keep_partial;
28 +extern int am_generator;
29 extern int log_got_error;
30 extern char *partial_dir;
32 @@ -149,8 +150,13 @@ void _exit_cleanup(int code, const char
38 log_exit(code, file, line);
40 + db_log_exit(code,file,line);
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 @@ -392,6 +392,9 @@ static int rsync_module(int f_in, int f_
50 XFLG_ABS_IF_SLASH | XFLG_OLD_PREFIXES);
58 if (*lp_prexfer_exec(i) || *lp_postxfer_exec(i)) {
59 @@ -630,6 +633,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);
67 rprintf(FLOG, "rsync %s %s from %s (%s)\n",
68 am_sender ? "on" : "to",
71 @@ -540,6 +540,12 @@ if test x"$with_included_popt" != x"yes"
72 AC_CHECK_LIB(popt, poptGetContext, , [with_included_popt=yes])
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) ])
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
87 +error type description
90 +2 file/dir deletion failed
93 +5 multiplexing overflow
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
105 +17 rsync hack failed
106 +18 "invalid basis_dir index
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
120 +32 failed to open file/directory
122 --- old/dblog-tables-mysql.sql
123 +++ new/dblog-tables-mysql.sql
125 +drop table transfer;
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,
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),
150 + foreign key (session_id) references session (id)
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,
166 + foreign key (session_id) references session (id)
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),
177 + foreign key (session_id) references session (id)
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,
187 + foreign key (session_id) references session (id)
189 --- old/dblog-tables-postgresql.sql
190 +++ new/dblog-tables-postgresql.sql
192 +drop table transfer;
195 +drop sequence session_id_seq;
196 +create sequence session_id_seq;
198 +CREATE TABLE "session" (
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,
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),
219 + foreign key (session_id) references session (id)
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,
235 + foreign key (session_id) references session (id)
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),
246 + foreign key (session_id) references session (id)
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,
257 + foreign key (session_id) references session (id)
263 + * ODBC Database logging functions
265 + * Written by Steve Sether, April 2004
266 + * steve@vellmont.com
274 +#ifdef HAVE_ODBC_SQL_H
275 +#include <odbc/sql.h>
279 +#ifdef HAVE_SQLEXT_H
282 +#ifdef HAVE_ODBC_SQLEXT_H
283 +#include <odbc/sqlext.h>
287 +#ifdef HAVE_SQLTYPES_H
288 +#include <sqltypes.h>
290 +#ifdef HAVE_ODBC_SQLTYPES_H
291 +#include <odbc/sqltypes.h>
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;
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;
316 +/* This function simply removes invalid characters from the SQL statement
317 + * to prevent SQL injection attacks. */
318 +char *sanitizeSql(const char *input)
323 + if (strlen(input) > ((~(unsigned int)0)>>1)-3)
325 + if (!(out = ptr = new_array(char, strlen(input) * 2 + 1)))
328 + for (c = input; *c; c++) {
359 +void db_log_open(void)
361 + if (!lp_database_logging(module_id))
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");
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;
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;
389 + /* Set connection attributes for the generator db connection */
390 + SQLSetConnectAttr(db_handle_g, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
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);
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;
407 + rprintf(FLOG,"Connected to database for generator!\n");
409 + rprintf(FERROR,"Already connected to database for generator\n");
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;
421 + /* Set connection attributes for the receiver db connection */
422 + SQLSetConnectAttr(db_handle_r, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
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);
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;
439 + rprintf(FLOG,"Connected to database for receiver!\n");
441 + rprintf(FERROR,"Already connected to database for receiver\n");
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;
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;
473 + if (!lp_database_logging(module_id))
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;
482 + rprintf(FERROR,"No generator sql statement handle to close\n");
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;
491 + rprintf(FERROR,"Generator database connection already closed\n");
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;
499 + rprintf(FERROR,"No receiver sql statement handle to close\n");
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;
508 + rprintf(FERROR,"Receiver database connection already closed\n");
512 + if (db_environ_handle != NULL) {
513 + /* free the environment handle */
514 + SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
515 + db_environ_handle = NULL;
517 + rprintf(FERROR,"No environment handle to close\n");
521 +static long get_unique_session_id()
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;
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));
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);
561 + result = SQLFetch(sql_statement_handle);
562 + if (result != SQL_NO_DATA && unique != 0) {
563 + rprintf(FINFO,"Got unique sequence! %ld\n",unique);
565 + rprintf(FERROR,"Error at get_sequence: Didn't get unique session ID\n");
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);
577 + rprintf(FERROR,"Error at get_sequence: Not connected to database\n");
581 +void db_log_session()
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;
588 + if (!lp_database_logging(module_id))
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();
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());
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());
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);
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);
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();
633 + rprintf(FERROR,"Error at db_log_session: Not connected to database!\n");
637 +void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation)
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;
649 + if (!lp_database_logging(module_id))
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;
659 + clean_fname(strFileNamePtr, 0);
660 + if (*strFileNamePtr == '/')
663 + snprintf(strFileSize, sizeof strFileSize, "%.0f", (double)file->length);
665 + intBytesTransferred = stats.total_written - initial_stats->total_written;
667 + intBytesTransferred = stats.total_read - initial_stats->total_read;
671 + intCheckSumBytes = stats.total_written - initial_stats->total_written;
673 + intCheckSumBytes = stats.total_read - initial_stats->total_read;
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");
689 + rprintf(FERROR,"Error at db_log_transfer: Not connected to database!\n");
693 +void db_log_exit(int code, const char *file, int line)
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;
701 + if (!lp_database_logging(module_id))
704 + if (db_handle != NULL) {
706 + error_text = rerr_name(code);
708 + error_text = "unexplained error";
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());
718 + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
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);
725 + rprintf(FERROR,"Error at db_log_exit: Not connected to database!\n");
729 +void db_log_delete(char *fname, int mode)
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;
735 + if (!am_daemon || dry_run || !lp_database_logging(module_id))
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);
743 + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
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);
750 + rprintf(FERROR,"Error at db_log_delete: Not connected to database!\n");
754 +void db_log_error(enum logcode code, int errcode, const char *format,...)
756 + char strSqlStatement[MAXPATHLEN+1024];
758 + char buf[MAXPATHLEN+512];
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;
763 + if (!lp_database_logging(module_id))
766 + va_start(ap, format);
767 + len = vsnprintf(buf, sizeof buf, format, ap);
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[] = "[...]";
776 + /* Reset length, and zero-terminate the end of our buffer */
777 + len = sizeof buf - 1;
780 + /* Copy the ellipsis to the end of the string, but give
781 + * us one extra character:
783 + * v--- null byte at buf[sizeof buf - 1]
785 + * -> abcd[...]00 <-- now two null bytes at end
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') {
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));
801 + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
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);
808 + rprintf(FERROR,"Error at db_log_error: Not connected to database!\n");
814 +This patch adds the following options:
817 + If set to True, rsync will attempt to connect to
818 + the specified datasource and write to the named tables.
821 +"database datasource"
822 + Specifies the name of the ODBC data source to use.
825 + The username to use when connecting to the database.
828 + The password to use when connecting to the database.
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.
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.
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).
848 + The name of the table to log deleted files/directories to.
852 + The name of the table errors will be logged to.
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:
862 + uses the syntax of nextval for PostgreSQL databases
865 + uses the syntax of nextval for Oracle databases
868 + uses the syntax of nextval for DB2 databases
871 + uses the last_insert_id() command for the MySQL databases
874 + uses the @@IDENTITY command for Sybase databases
877 + Define your own method to get a unique identifier. See the
878 + "custom unique id select", and the "get custom id before select"
882 + If your database supports sequences, list the name of the sequence to use
883 + for the session unique identifier.
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.
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.
900 @@ -120,9 +120,16 @@ typedef struct
904 + char *custom_unique_id_select;
905 + char *database_datasource;
906 + char *database_password;
907 + char *database_username;
908 + char *delete_table_name;
910 + char *error_table_name;
913 + char *exit_table_name;
917 @@ -139,13 +146,19 @@ typedef struct
919 char *refuse_options;
921 + char *sequence_name;
922 + char *session_table_name;
924 + char *transfer_table_name;
926 + char *unique_id_method;
932 + BOOL database_logging;
933 + BOOL get_custom_id_before_insert;
935 BOOL ignore_nonreadable;
937 @@ -165,9 +178,16 @@ static service sDefault =
939 /* auth_users; */ NULL,
941 + /* custom_unique_id_select; */ NULL,
942 + /* database_datasource; */ NULL,
943 + /* database_password; */ NULL,
944 + /* database_username; */ NULL,
945 + /* delete_table_name; */ NULL,
946 /* dont_compress; */ "*.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz",
947 + /* error_table_name; */ NULL,
949 /* exclude_from; */ NULL,
950 + /* exit_table_name; */ NULL,
952 /* gid; */ NOBODY_GROUP,
953 /* hosts_allow; */ NULL,
954 @@ -184,13 +204,19 @@ static service sDefault =
955 /* prexfer_exec; */ NULL,
956 /* refuse_options; */ NULL,
957 /* secrets_file; */ NULL,
958 + /* sequence_name; */ NULL,
959 + /* session_table_name; */ NULL,
960 /* temp_dir; */ NULL,
961 + /* transfer_table_name; */ NULL,
962 /* uid; */ NOBODY_USER,
963 + /* unique_id_method; */ NULL,
965 /* max_connections; */ 0,
966 /* max_verbosity; */ 1,
969 + /* database_logging; */ False,
970 + /* get_custom_id_before_insert; */ True,
971 /* ignore_errors; */ False,
972 /* ignore_nonreadable; */ False,
974 @@ -291,10 +317,19 @@ static struct parm_struct parm_table[] =
976 {"auth users", P_STRING, P_LOCAL, &sDefault.auth_users, NULL,0},
977 {"comment", P_STRING, P_LOCAL, &sDefault.comment, NULL,0},
978 + {"custom unique id select",P_STRING,P_LOCAL,&sDefault.custom_unique_id_select,NULL,0},
979 + {"database datasource",P_STRING,P_LOCAL, &sDefault.database_datasource,NULL,0},
980 + {"database logging", P_BOOL, P_LOCAL, &sDefault.database_logging, NULL,0},
981 + {"database password", P_STRING, P_LOCAL, &sDefault.database_password, NULL,0},
982 + {"database username", P_STRING, P_LOCAL, &sDefault.database_username, NULL,0},
983 + {"delete table name", P_STRING, P_LOCAL, &sDefault.delete_table_name, NULL,0},
984 {"dont compress", P_STRING, P_LOCAL, &sDefault.dont_compress, NULL,0},
985 + {"error table name", P_STRING, P_LOCAL, &sDefault.error_table_name, NULL,0},
986 {"exclude from", P_STRING, P_LOCAL, &sDefault.exclude_from, NULL,0},
987 {"exclude", P_STRING, P_LOCAL, &sDefault.exclude, NULL,0},
988 + {"exit table name", P_STRING, P_LOCAL, &sDefault.exit_table_name, NULL,0},
989 {"filter", P_STRING, P_LOCAL, &sDefault.filter, NULL,0},
990 + {"get custom id before insert",P_BOOL,P_LOCAL,&sDefault.get_custom_id_before_insert,NULL,0},
991 {"gid", P_STRING, P_LOCAL, &sDefault.gid, NULL,0},
992 {"hosts allow", P_STRING, P_LOCAL, &sDefault.hosts_allow, NULL,0},
993 {"hosts deny", P_STRING, P_LOCAL, &sDefault.hosts_deny, NULL,0},
994 @@ -318,11 +353,15 @@ static struct parm_struct parm_table[] =
995 {"read only", P_BOOL, P_LOCAL, &sDefault.read_only, NULL,0},
996 {"refuse options", P_STRING, P_LOCAL, &sDefault.refuse_options, NULL,0},
997 {"secrets file", P_STRING, P_LOCAL, &sDefault.secrets_file, NULL,0},
998 + {"sequence name", P_STRING, P_LOCAL, &sDefault.sequence_name, NULL,0},
999 + {"session table name",P_STRING, P_LOCAL, &sDefault.session_table_name,NULL,0},
1000 {"strict modes", P_BOOL, P_LOCAL, &sDefault.strict_modes, NULL,0},
1001 {"temp dir", P_PATH, P_LOCAL, &sDefault.temp_dir, NULL,0},
1002 {"timeout", P_INTEGER,P_LOCAL, &sDefault.timeout, NULL,0},
1003 {"transfer logging", P_BOOL, P_LOCAL, &sDefault.transfer_logging, NULL,0},
1004 + {"transfer table name",P_STRING,P_LOCAL, &sDefault.transfer_table_name,NULL,0},
1005 {"uid", P_STRING, P_LOCAL, &sDefault.uid, NULL,0},
1006 + {"unique id method", P_STRING, P_LOCAL, &sDefault.unique_id_method, NULL,0},
1007 {"use chroot", P_BOOL, P_LOCAL, &sDefault.use_chroot, NULL,0},
1008 {"write only", P_BOOL, P_LOCAL, &sDefault.write_only, NULL,0},
1009 {NULL, P_BOOL, P_NONE, NULL, NULL,0}
1010 @@ -383,9 +422,16 @@ FN_GLOBAL_INTEGER(lp_syslog_facility, &G
1012 FN_LOCAL_STRING(lp_auth_users, auth_users)
1013 FN_LOCAL_STRING(lp_comment, comment)
1014 +FN_LOCAL_STRING(lp_custom_unique_id_select,custom_unique_id_select)
1015 +FN_LOCAL_STRING(lp_database_datasource, database_datasource)
1016 +FN_LOCAL_STRING(lp_database_password, database_password)
1017 +FN_LOCAL_STRING(lp_database_username, database_username)
1018 +FN_LOCAL_STRING(lp_delete_table_name,delete_table_name)
1019 FN_LOCAL_STRING(lp_dont_compress, dont_compress)
1020 +FN_LOCAL_STRING(lp_error_table_name,error_table_name)
1021 FN_LOCAL_STRING(lp_exclude, exclude)
1022 FN_LOCAL_STRING(lp_exclude_from, exclude_from)
1023 +FN_LOCAL_STRING(lp_exit_table_name, exit_table_name)
1024 FN_LOCAL_STRING(lp_filter, filter)
1025 FN_LOCAL_STRING(lp_gid, gid)
1026 FN_LOCAL_STRING(lp_hosts_allow, hosts_allow)
1027 @@ -402,13 +448,19 @@ FN_LOCAL_STRING(lp_postxfer_exec, postxf
1028 FN_LOCAL_STRING(lp_prexfer_exec, prexfer_exec)
1029 FN_LOCAL_STRING(lp_refuse_options, refuse_options)
1030 FN_LOCAL_STRING(lp_secrets_file, secrets_file)
1031 +FN_LOCAL_STRING(lp_sequence_name,sequence_name)
1032 +FN_LOCAL_STRING(lp_session_table_name,session_table_name)
1033 FN_LOCAL_STRING(lp_temp_dir, temp_dir)
1034 +FN_LOCAL_STRING(lp_transfer_table_name, transfer_table_name)
1035 FN_LOCAL_STRING(lp_uid, uid)
1036 +FN_LOCAL_STRING(lp_unique_id_method,unique_id_method)
1038 FN_LOCAL_INTEGER(lp_max_connections, max_connections)
1039 FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity)
1040 FN_LOCAL_INTEGER(lp_timeout, timeout)
1042 +FN_LOCAL_BOOL(lp_database_logging, database_logging)
1043 +FN_LOCAL_BOOL(lp_get_custom_id_before_insert,get_custom_id_before_insert)
1044 FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
1045 FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
1046 FN_LOCAL_BOOL(lp_list, list)
1049 @@ -94,7 +94,7 @@ struct {
1051 * Map from rsync error code to name, or return NULL.
1053 -static char const *rerr_name(int code)
1054 +char const *rerr_name(int code)
1057 for (i = 0; rerr_names[i].name; i++) {
1060 @@ -168,6 +168,9 @@ static void handle_stats(int f)
1063 log_exit(0, __FILE__, __LINE__);
1064 +#ifdef HAVE_LIBODBC
1065 + db_log_exit(0,__FILE__,__LINE__);
1067 if (f == -1 || !am_sender)
1072 @@ -113,6 +113,10 @@ static int get_tmpname(char *fnametmp, c
1075 rprintf(FERROR, "temporary filename too long: %s\n", fname);
1076 +#ifdef HAVE_LIBODBC
1077 + db_log_error(FERROR,13, "temporary filename too long: %s\n",
1083 @@ -229,6 +233,11 @@ static int receive_data(int f_in, char *
1084 rsyserr(FERROR, errno,
1085 "lseek failed on %s",
1087 +#ifdef HAVE_LIBODBC
1088 + db_log_error(FERROR, 14,
1089 + "lseek failed on %s",
1090 + full_fname(fname));
1092 exit_cleanup(RERR_FILEIO);
1095 @@ -254,6 +263,9 @@ static int receive_data(int f_in, char *
1097 rsyserr(FERROR, errno, "write failed on %s",
1099 +#ifdef HAVE_LIBODBC
1100 + db_log_error(FERROR, 15, "write failed on %s",full_fname(fname));
1102 exit_cleanup(RERR_FILEIO);
1105 @@ -297,6 +309,12 @@ static void handle_delayed_updates(struc
1106 rsyserr(FERROR, errno,
1107 "rename failed for %s (from %s)",
1108 full_fname(fname), partialptr);
1109 +#ifdef HAVE_LIBODBC
1110 + db_log_error(FERROR, 16,
1111 + "rename failed for %s (from %s)",
1112 + full_fname(fname),
1116 if (remove_sent_files
1117 || (preserve_hard_links
1118 @@ -419,6 +437,9 @@ int recv_files(int f_in, struct file_lis
1119 if (server_filter_list.head
1120 && check_filter(&server_filter_list, fname, 0) < 0) {
1121 rprintf(FERROR, "attempt to hack rsync failed.\n");
1122 +#ifdef HAVE_LIBODBC
1123 + db_log_error(FERROR,17,"attempt to hack rsync failed.");
1125 exit_cleanup(RERR_PROTOCOL);
1128 @@ -474,6 +495,11 @@ int recv_files(int f_in, struct file_lis
1130 "invalid basis_dir index: %d.\n",
1132 +#ifdef HAVE_LIBODBC
1133 + db_log_error(FERROR, 18,
1134 + "invalid basis_dir index: %d.\n",
1137 exit_cleanup(RERR_PROTOCOL);
1139 pathjoin(fnamecmpbuf, sizeof fnamecmpbuf,
1140 @@ -519,6 +545,9 @@ int recv_files(int f_in, struct file_lis
1141 if (fd1 != -1 && do_fstat(fd1,&st) != 0) {
1142 rsyserr(FERROR, errno, "fstat %s failed",
1143 full_fname(fnamecmp));
1144 +#ifdef HAVE_LIBODBC
1145 + db_log_error(FERROR, 19,"fstat %s failed",full_fname(fnamecmp));
1147 discard_receive_data(f_in, file->length);
1150 @@ -532,6 +561,9 @@ int recv_files(int f_in, struct file_lis
1152 rprintf(FERROR,"recv_files: %s is a directory\n",
1153 full_fname(fnamecmp));
1154 +#ifdef HAVE_LIBODBC
1155 + db_log_error(FERROR,20,"recv_files: %s is a directory",full_fname(fnamecmp));
1157 discard_receive_data(f_in, file->length);
1160 @@ -555,6 +587,9 @@ int recv_files(int f_in, struct file_lis
1162 rsyserr(FERROR, errno, "open %s failed",
1164 +#ifdef HAVE_LIBODBC
1165 + db_log_error(FERROR,22, "open %s failed", full_fname(fname));
1167 discard_receive_data(f_in, file->length);
1170 @@ -588,6 +623,10 @@ int recv_files(int f_in, struct file_lis
1172 rsyserr(FERROR, errno, "mkstemp %s failed",
1173 full_fname(fnametmp));
1174 +#ifdef HAVE_LIBODBC
1175 + db_log_error(FERROR, 22, "mkstemp %s failed",
1176 + full_fname(fnametmp));
1178 discard_receive_data(f_in, file->length);
1181 @@ -610,12 +649,19 @@ int recv_files(int f_in, struct file_lis
1183 if (!log_before_transfer)
1184 log_item(file, &initial_stats, iflags, NULL);
1185 +#ifdef HAVE_LIBODBC
1186 + db_log_transfer(file, &initial_stats, "receive");
1191 if (close(fd2) < 0) {
1192 rsyserr(FERROR, errno, "close failed on %s",
1193 full_fname(fnametmp));
1194 +#ifdef HAVE_LIBODBC
1195 + db_log_error(FERROR, 23, "close failed on %s",
1196 + full_fname(fnametmp));
1198 exit_cleanup(RERR_FILEIO);
1201 @@ -669,6 +715,12 @@ int recv_files(int f_in, struct file_lis
1203 "%s: %s failed verification -- update %s%s.\n",
1204 errstr, fname, keptstr, redostr);
1205 +#ifdef HAVE_LIBODBC
1206 + db_log_error(msgtype,24,
1207 + "%s: %s failed verification -- update %s%s.\n",
1209 + keptstr, redostr);
1213 SIVAL(numbuf, 0, i);
1216 @@ -352,6 +352,9 @@ void send_files(struct file_list *flist,
1218 if (!log_before_transfer)
1219 log_item(file, &initial_stats, iflags, NULL);
1220 +#ifdef HAVE_LIBODBC
1221 + db_log_transfer(file, &initial_stats,"send");
1225 j = unmap_file(mbuf);