Add support for logging daemon messages to an SQL database. To use this patch, run these commands for a successful build: patch -p1 +#else +#ifdef HAVE_ODBC_SQL_H +#include +#endif +#endif + +#ifdef HAVE_SQLEXT_H +#include +#else +#ifdef HAVE_ODBC_SQLEXT_H +#include +#endif +#endif + +#ifdef HAVE_SQLTYPES_H +#include +#else +#ifdef HAVE_ODBC_SQLTYPES_H +#include +#endif +#endif + +SQLHENV db_environ_handle; /* Handle ODBC environment */ +long result; /* result of functions */ +SQLHDBC db_handle_g = NULL; /* database connection handle for generator*/ +SQLHDBC db_handle_r = NULL; /* database connection handle for sender */ +SQLHSTMT sql_statement_handle_g; /* SQL statement handle for generator*/ +SQLHSTMT sql_statement_handle_r; /* SQL statement handle for receiver*/ +extern int am_daemon; +extern int am_sender; +extern int am_generator; +extern char *auth_user; +extern int module_id; +extern int dry_run; + + +char sql_status[10]; /* Status SQL */ +SQLINTEGER V_OD_err, V_OD_rowanz, V_OD_id; +SQLSMALLINT V_OD_mlen, V_OD_colanz; +char V_OD_msg[200], V_OD_buffer[200]; +SQLINTEGER session_id; + + +/* This function simply removes invalid characters from the SQL statement + * to prevent SQL injection attacks. */ +char *sanitizeSql(const char *input) +{ + char *out, *ptr; + const char *c; + + if (strlen(input) > ((~(unsigned int)0)>>1)-3) + return 0; + if (!(out = ptr = new_array(char, strlen(input) * 2 + 1))) + return 0; + + for (c = input; *c; c++) { + switch (*c) { + case '\'': + *ptr++ = '\''; + *ptr++ = '\''; + break; + case '\b': + *ptr++ = '\\'; + *ptr++ = 'b'; + break; + case '\n': + *ptr++ = '\\'; + *ptr++ = 'n'; + break; + case '\r': + *ptr++ = '\\'; + *ptr++ = 'r'; + break; + case '\t': + *ptr++ = '\\'; + *ptr++ = 't'; + break; + default: + *ptr++ = *c; + break; + } + } + *ptr = '\0'; + return out; +} + +void db_log_open(void) +{ + if (!lp_database_logging(module_id)) + return; + + /* get ODBC environment handle */ + result = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&db_environ_handle); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + rprintf(FERROR, "Error: couldn't get database environment handle\n"); + return; + } + + /* Setting database enviroment */ + result = SQLSetEnvAttr(db_environ_handle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + rprintf(FERROR, "Error: couldn't set database environment.\n"); + SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle); + db_environ_handle = NULL; + return; + } + if (db_handle_g == NULL) { + /* Get a database handle for the generator*/ + result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle_g); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + rprintf(FERROR, "Error: couldn't allocate database handle for generator\n"); + SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle); + db_environ_handle = NULL; + return; + } + + /* Set connection attributes for the generator db connection */ + SQLSetConnectAttr(db_handle_g, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0); + + /* get the database connection for the generator. */ + result = SQLConnect(db_handle_g, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS, + (SQLCHAR*) lp_database_username(module_id), SQL_NTS, + (SQLCHAR*) lp_database_password(module_id), SQL_NTS); + + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_g, 1, + sql_status, &V_OD_err, V_OD_msg, 100, &V_OD_mlen); + rprintf(FERROR,"Error Connecting to Database (generator) %s\n",V_OD_msg); + SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g); + db_handle_g = NULL; + SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle); + db_environ_handle = NULL; + return; + } + rprintf(FLOG,"Connected to database for generator!\n"); + } else { + rprintf(FERROR,"Already connected to database for generator\n"); + } + if (db_handle_r == NULL) { + /* Get a database handle for the receiver */ + result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle_r); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + rprintf(FERROR, "Error: couldn't allocate database handle for receiver\n"); + SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle); + db_environ_handle = NULL; + return; + } + + /* Set connection attributes for the receiver db connection */ + SQLSetConnectAttr(db_handle_r, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0); + + /* get the generator connection for the receiver. */ + result = SQLConnect(db_handle_r, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS, + (SQLCHAR*) lp_database_username(module_id), SQL_NTS, + (SQLCHAR*) lp_database_password(module_id), SQL_NTS); + + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_r,1, + sql_status, &V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error Connecting to Database (receiver) %s\n",V_OD_msg); + SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r); + db_handle_r = NULL; + SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle); + db_environ_handle = NULL; + return; + } + rprintf(FLOG,"Connected to database for receiver!\n"); + } else { + rprintf(FERROR,"Already connected to database for receiver\n"); + } + + /* get SQL statement handle for generator */ + result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle_g, &sql_statement_handle_g); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_g,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg); + SQLDisconnect(db_handle_g); + SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g); + db_handle_g = NULL; + SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle); + db_environ_handle = NULL; + return; + } + + /* get SQL statement handle for receiver */ + result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle_r, &sql_statement_handle_r); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_r,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg); + SQLDisconnect(db_handle_r); + SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r); + db_handle_r = NULL; + SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle); + db_environ_handle = NULL; + return; + } +} + +void db_log_close() +{ + if (!lp_database_logging(module_id)) + return; + + if (am_generator) { + if (sql_statement_handle_g != NULL) { + /* free the statement handle first */ + SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle_g); + sql_statement_handle_g = NULL; + } else { + rprintf(FERROR,"No generator sql statement handle to close\n"); + } + + if (db_handle_g != NULL) { + /* disconnect, and free the database handle. */ + SQLDisconnect(db_handle_g); + SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g); + db_handle_g = NULL; + } else { + rprintf(FERROR,"Generator database connection already closed\n"); + } + } else { /* must be receiver */ + if (sql_statement_handle_r != NULL) { + /* free the statement handle first */ + SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle_r); + sql_statement_handle_r = NULL; + } else { + rprintf(FERROR,"No receiver sql statement handle to close\n"); + } + + if (db_handle_r != NULL) { + /* disconnect, and free the database handle. */ + SQLDisconnect(db_handle_r); + SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r); + db_handle_r = NULL; + } else { + rprintf(FERROR,"Receiver database connection already closed\n"); + } + } + + if (db_environ_handle != NULL) { + /* free the environment handle */ + SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle); + db_environ_handle = NULL; + } else { + rprintf(FERROR,"No environment handle to close\n"); + } +} + +static long get_unique_session_id() +{ + long unique; + char strSqlStatement[1024]; + SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r; + SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r; + + if (db_handle != NULL) { + /* choose the appropriate select statement based upon which DBMS we're using. + * different datbases use different methods to get a unique ID. Some use a counter + * object (sequence), others use an auto increment datatype and have a method + * to get the last ID inserted using this connection. */ + if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0) { + snprintf(strSqlStatement, sizeof strSqlStatement, + "SELECT NEXTVAL('%s');", lp_sequence_name(module_id)); + } else if (strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0) { + snprintf(strSqlStatement, sizeof strSqlStatement, + "SELECT %s.NEXTVAL FROM dual;", lp_sequence_name(module_id)); + } else if (strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0) { + snprintf(strSqlStatement, sizeof strSqlStatement, + "VALUES NEXTVAL FOR %s;",lp_sequence_name(module_id)); + } else if (strcmp(lp_unique_id_method(module_id),"last_insert_id") == 0) { /* MySql */ + snprintf(strSqlStatement, sizeof strSqlStatement, + "SELECT LAST_INSERT_ID()"); + } else if (strcmp(lp_unique_id_method(module_id),"@@IDENTITY") == 0) { /* Sybase */ + snprintf(strSqlStatement, sizeof strSqlStatement, + "SELECT @@IDENTITY"); + } else if (strcmp(lp_unique_id_method(module_id),"custom") == 0){ /* Users custom statement */ + snprintf(strSqlStatement, sizeof strSqlStatement, + lp_custom_unique_id_select(module_id)); + } + + /* bind the 1st column to unique */ + SQLBindCol(sql_statement_handle,1,SQL_C_LONG,&unique,150,&V_OD_err); + /* execute the SQL statement */ + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error at get_sequence: Error in Select! %s %s\n",strSqlStatement,V_OD_msg); + } else { + result = SQLFetch(sql_statement_handle); + if (result != SQL_NO_DATA && unique != 0) { + rprintf(FINFO,"Got unique sequence! %ld\n",unique); + } else { + rprintf(FERROR,"Error at get_sequence: Didn't get unique session ID\n"); + } + /* Close the cursor so the statement can be re-used */ + result = SQLFreeStmt(sql_statement_handle,SQL_CLOSE); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error at get_sequence: Error in closing SQL statement handle %s\n",V_OD_msg); + return unique; + } + return unique; + } + } + rprintf(FERROR,"Error at get_sequence: Not connected to database\n"); + return -1; +} + +void db_log_session() +{ + char strSqlStatement[1024]; + int gotSessionID = 0; + SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r; + SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r; + + if (!lp_database_logging(module_id)) + return; + + if (db_handle != NULL) { + /* if we're using a sequence via the nextval command to + * get a unique ID, we need to get it before we do the + * insert. We also get the unique ID now if custom, + * and get_custom_id_before_insert is set. */ + if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0 + || strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0 + || strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0 + || (strcmp(lp_unique_id_method(module_id),"custom") == 0 + && lp_get_custom_id_before_insert(module_id))) { + session_id = get_unique_session_id(); + gotSessionID = 1; + snprintf(strSqlStatement, sizeof strSqlStatement, + "INSERT INTO %s (id, date, ip_address, username, module_name, module_path, process_id) VALUES ('%ld', '%s', '%s', '%s','%s','%s','%d');", + lp_session_table_name(module_id), session_id, timestring(time(NULL)), client_addr(0), + auth_user, lp_name(module_id), lp_path(module_id), getpid()); + } else { + /* Otherwise the ID gets created automatically, and we get the ID it used after the insert. */ + snprintf(strSqlStatement, sizeof strSqlStatement, + "INSERT INTO %s (date, ip_address, username, module_name, module_path, process_id) VALUES ('%s', '%s', '%s', '%s','%s','%d');", + lp_session_table_name(module_id), timestring(time(NULL)), client_addr(0), auth_user, + lp_name(module_id), lp_path(module_id), getpid()); + } + + /* Insert the new session into the database */ + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error at db_log_session: Error in Insert %s %s\n",strSqlStatement,V_OD_msg); + } + + /* close the cursor so the statement handle can be re-used. */ + result = SQLFreeStmt(sql_statement_handle,SQL_CLOSE); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error in resetting SQL statement handle %s\n",V_OD_msg); + } + /* get the session ID for databases that give the unique ID after an insert */ + if (gotSessionID == 0) { + session_id = get_unique_session_id(); + } + } else { + rprintf(FERROR,"Error at db_log_session: Not connected to database!\n"); + } +} + +void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation) +{ + extern struct stats stats; + char strSqlStatement[2048]; + char strFileName[MAXPATHLEN]; + char *strFileNamePtr; + char strFileSize[255]; + int64 intBytesTransferred; + int64 intCheckSumBytes; + SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r; + SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r; + + if (!lp_database_logging(module_id)) + return; + + if (db_handle != NULL) { + strFileNamePtr = f_name(file, NULL); + if (am_sender && file->dir.root) { + pathjoin(strFileName, sizeof strFileName, + file->dir.root, strFileNamePtr); + strFileNamePtr = strFileName; + } + clean_fname(strFileNamePtr, 0); + if (*strFileNamePtr == '/') + strFileNamePtr++; + + snprintf(strFileSize, sizeof strFileSize, "%.0f", (double)F_LENGTH(file)); + if (am_sender) { + intBytesTransferred = stats.total_written - initial_stats->total_written; + } else { + intBytesTransferred = stats.total_read - initial_stats->total_read; + } + + if (!am_sender) { + intCheckSumBytes = stats.total_written - initial_stats->total_written; + } else { + intCheckSumBytes = stats.total_read - initial_stats->total_read; + } + + snprintf(strSqlStatement, sizeof strSqlStatement, + "INSERT INTO %s (session_id,date, file_name, file_size, bytes_transferred, checksum_bytes_transferred, operation) VALUES ('%ld','%s','%s','%s','%Ld','%Ld','%s');", + lp_transfer_table_name(module_id), session_id, timestring(time(NULL)), + sanitizeSql(strFileNamePtr), strFileSize, intBytesTransferred, + intCheckSumBytes, operation); + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error at db_log_transfer: Error in Insert %s %s\n",strSqlStatement,V_OD_msg); + if (result == SQL_INVALID_HANDLE) + rprintf(FERROR,"INVALID HANDLE\n"); + } + } else { + rprintf(FERROR,"Error at db_log_transfer: Not connected to database!\n"); + } +} + +void db_log_exit(int code, const char *file, int line) +{ + char strSqlStatement[2048]; + const char *error_text; + extern struct stats stats; + SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r; + SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r; + + if (!lp_database_logging(module_id)) + return; + + if (db_handle != NULL) { + if (code != 0) { + error_text = rerr_name(code); + if (!error_text) { + error_text = "unexplained error"; + } + } else { + error_text = ""; + } + snprintf(strSqlStatement, sizeof strSqlStatement, + "INSERT INTO %s (session_id, date, total_bytes_written,total_bytes_read,total_size,error_text,error_code,error_file,error_line,process_id) VALUES ('%ld','%s','%Ld','%Ld','%Ld','%s','%d','%s','%d','%d');", + lp_exit_table_name(module_id), session_id, timestring(time(NULL)), stats.total_written, + stats.total_read, stats.total_size, error_text, code, file, line, getpid()); + + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS); + + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error at db_log_exit: Error in Insert %s %s\n",strSqlStatement,V_OD_msg); + } + } else { + rprintf(FERROR,"Error at db_log_exit: Not connected to database!\n"); + } +} + +void db_log_delete(char *fname, int mode) +{ + char strSqlStatement[2048]; + SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r; + SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r; + + if (!am_daemon || dry_run || !lp_database_logging(module_id)) + return; + + if (db_handle != NULL) { + snprintf(strSqlStatement, sizeof strSqlStatement, + "INSERT INTO %s (session_id, date, path, mode) VALUES ('%ld','%s','%s','%d');", + lp_delete_table_name(module_id), session_id, timestring(time(NULL)), sanitizeSql(fname), mode); + + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS); + + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error at db_log_delete: Error in Insert %s %s\n",strSqlStatement,V_OD_msg); + } + } else { + rprintf(FERROR,"Error at db_log_delete: Not connected to database!\n"); + } +} + +void db_log_error(enum logcode code, int errcode, const char *format,...) +{ + char strSqlStatement[MAXPATHLEN+1024]; + va_list ap; + char buf[MAXPATHLEN+512]; + size_t len; + SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r; + SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r; + + if (!lp_database_logging(module_id)) + return; + + va_start(ap, format); + len = vsnprintf(buf, sizeof buf, format, ap); + va_end(ap); + + /* Deal with buffer overruns. Instead of panicking, just + * truncate the resulting string. (Note that configure ensures + * that we have a vsnprintf() that doesn't ever return -1.) */ + if (len > sizeof buf - 1) { + const char ellipsis[] = "[...]"; + + /* Reset length, and zero-terminate the end of our buffer */ + len = sizeof buf - 1; + buf[len] = '\0'; + + /* Copy the ellipsis to the end of the string, but give + * us one extra character: + * + * v--- null byte at buf[sizeof buf - 1] + * abcdefghij0 + * -> abcd[...]00 <-- now two null bytes at end + * + * If the input format string has a trailing newline, + * we copy it into that extra null; if it doesn't, well, + * all we lose is one byte. */ + strncpy(buf+len-sizeof ellipsis, ellipsis, sizeof ellipsis); + if (format[strlen(format)-1] == '\n') { + buf[len-1] = '\n'; + } + } + + if (db_handle != NULL) { + snprintf(strSqlStatement, sizeof strSqlStatement, + "INSERT INTO %s (session_id, date, logcode, error_number, error_text) VALUES ('%ld','%s','%d','%d','%s');", + lp_error_table_name(module_id), session_id, timestring(time(NULL)), code, errcode, sanitizeSql(buf)); + + result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS); + + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen); + rprintf(FERROR,"Error at db_log_error: Error in Insert %s %s\n",strSqlStatement,V_OD_msg); + } + } else { + rprintf(FERROR,"Error at db_log_error: Not connected to database!\n"); + } +} --- old/instructions +++ new/instructions @@ -0,0 +1,84 @@ +This patch adds the following options: + +"database logging" + If set to True, rsync will attempt to connect to + the specified datasource and write to the named tables. + Defaults to False. + +"database datasource" + Specifies the name of the ODBC data source to use. + +"database username" + The username to use when connecting to the database. + +"database password" + The password to use when connecting to the database. + +"transfer table name" + The name of the transfer table to log to. This table contains individual + filenames, file sizes, bytes transferred, checksum bytes transferred, + operation (send or receive), and a timestamp. + +"session table name" + The name of the session table to log to. This table contains the username, + module name, module path, ip address, process ID, and a timestamp. + +"exit table name" + + The name of the exit table to log to. This table contains the total bytes + read, total bytes written, total size of all files, error code, line the + error occured at, file the error occured at and the text of the error. + (most of which will be blank if the program exited normally). + +"delete table name" + + The name of the table to log deleted files/directories to. + +"error table name" + + The name of the table errors will be logged to. + +"unique id method" + Different databases use different methods to get a unique identifier. + Some databases support sequence objects, and use various forms of the + nextval command to retrieve a unique identifier from it. Other databases + support an autonumber field, and support different methds of retrieving + the ID used in the last insert. Valid values for this option are: + + nextval-postgres + uses the syntax of nextval for PostgreSQL databases + + nextval-oracle + uses the syntax of nextval for Oracle databases + + nextval-db2 + uses the syntax of nextval for DB2 databases + + last_insert_id + uses the last_insert_id() command for the MySQL databases + + @@IDENTITY + uses the @@IDENTITY command for Sybase databases + + custom + Define your own method to get a unique identifier. See the + "custom unique id select", and the "get custom id before select" + parameters. + +"sequence name" + If your database supports sequences, list the name of the sequence to use + for the session unique identifier. + +"custom unique id select" + Only used if you specify the custom method in "unique id method". This is + a SQL statement to be executed to get a unique ID. This SQL statement must + return one column with the unique ID to use for the session ID. Should be + used in concert with the "get custom id before select" parameter. + +"get custom id before insert" + This parameter is ignored unless the "unique id method" is set to custom. + If set to true, the "custom unique id select" statement will be executed + BEFORE the session row is inserted into the database. (as is done when a + sequence is used for unique IDs). If False the statement will be executed + after the session row is inserted (as is done when the session ID is + automatically generates unique IDs). Defaults to True. --- old/loadparm.c +++ new/loadparm.c @@ -124,9 +124,16 @@ typedef struct { char *auth_users; char *comment; + char *custom_unique_id_select; + char *database_datasource; + char *database_password; + char *database_username; + char *delete_table_name; char *dont_compress; + char *error_table_name; char *exclude; char *exclude_from; + char *exit_table_name; char *filter; char *gid; char *hosts_allow; @@ -144,15 +151,21 @@ typedef struct char *prexfer_exec; char *refuse_options; char *secrets_file; + char *sequence_name; + char *session_table_name; char *temp_dir; + char *transfer_table_name; char *uid; + char *unique_id_method; int max_connections; int max_verbosity; int syslog_facility; int timeout; + BOOL database_logging; BOOL fake_super; + BOOL get_custom_id_before_insert; BOOL ignore_errors; BOOL ignore_nonreadable; BOOL list; @@ -172,9 +185,16 @@ static service sDefault = { /* auth_users; */ NULL, /* comment; */ NULL, + /* custom_unique_id_select; */ NULL, + /* database_datasource; */ NULL, + /* database_password; */ NULL, + /* database_username; */ NULL, + /* delete_table_name; */ NULL, /* dont_compress; */ DEFAULT_DONT_COMPRESS, + /* error_table_name; */ NULL, /* exclude; */ NULL, /* exclude_from; */ NULL, + /* exit_table_name; */ NULL, /* filter; */ NULL, /* gid; */ NOBODY_GROUP, /* hosts_allow; */ NULL, @@ -192,15 +212,21 @@ static service sDefault = /* prexfer_exec; */ NULL, /* refuse_options; */ NULL, /* secrets_file; */ NULL, + /* sequence_name; */ NULL, + /* session_table_name; */ NULL, /* temp_dir; */ NULL, + /* transfer_table_name; */ NULL, /* uid; */ NOBODY_USER, + /* unique_id_method; */ NULL, /* max_connections; */ 0, /* max_verbosity; */ 1, /* syslog_facility; */ LOG_DAEMON, /* timeout; */ 0, + /* database_logging; */ False, /* fake_super; */ False, + /* get_custom_id_before_insert; */ True, /* ignore_errors; */ False, /* ignore_nonreadable; */ False, /* list; */ True, @@ -299,11 +325,20 @@ static struct parm_struct parm_table[] = {"auth users", P_STRING, P_LOCAL, &sDefault.auth_users, NULL,0}, {"comment", P_STRING, P_LOCAL, &sDefault.comment, NULL,0}, + {"custom unique id select",P_STRING,P_LOCAL,&sDefault.custom_unique_id_select,NULL,0}, + {"database datasource",P_STRING,P_LOCAL, &sDefault.database_datasource,NULL,0}, + {"database logging", P_BOOL, P_LOCAL, &sDefault.database_logging, NULL,0}, + {"database password", P_STRING, P_LOCAL, &sDefault.database_password, NULL,0}, + {"database username", P_STRING, P_LOCAL, &sDefault.database_username, NULL,0}, + {"delete table name", P_STRING, P_LOCAL, &sDefault.delete_table_name, NULL,0}, {"dont compress", P_STRING, P_LOCAL, &sDefault.dont_compress, NULL,0}, + {"error table name", P_STRING, P_LOCAL, &sDefault.error_table_name, NULL,0}, {"exclude from", P_STRING, P_LOCAL, &sDefault.exclude_from, NULL,0}, {"exclude", P_STRING, P_LOCAL, &sDefault.exclude, NULL,0}, + {"exit table name", P_STRING, P_LOCAL, &sDefault.exit_table_name, NULL,0}, {"fake super", P_BOOL, P_LOCAL, &sDefault.fake_super, NULL,0}, {"filter", P_STRING, P_LOCAL, &sDefault.filter, NULL,0}, + {"get custom id before insert",P_BOOL,P_LOCAL,&sDefault.get_custom_id_before_insert,NULL,0}, {"gid", P_STRING, P_LOCAL, &sDefault.gid, NULL,0}, {"hosts allow", P_STRING, P_LOCAL, &sDefault.hosts_allow, NULL,0}, {"hosts deny", P_STRING, P_LOCAL, &sDefault.hosts_deny, NULL,0}, @@ -328,12 +363,16 @@ static struct parm_struct parm_table[] = {"read only", P_BOOL, P_LOCAL, &sDefault.read_only, NULL,0}, {"refuse options", P_STRING, P_LOCAL, &sDefault.refuse_options, NULL,0}, {"secrets file", P_STRING, P_LOCAL, &sDefault.secrets_file, NULL,0}, + {"sequence name", P_STRING, P_LOCAL, &sDefault.sequence_name, NULL,0}, + {"session table name",P_STRING, P_LOCAL, &sDefault.session_table_name,NULL,0}, {"strict modes", P_BOOL, P_LOCAL, &sDefault.strict_modes, NULL,0}, {"syslog facility", P_ENUM, P_LOCAL, &sDefault.syslog_facility,enum_facilities,0}, {"temp dir", P_PATH, P_LOCAL, &sDefault.temp_dir, NULL,0}, {"timeout", P_INTEGER,P_LOCAL, &sDefault.timeout, NULL,0}, {"transfer logging", P_BOOL, P_LOCAL, &sDefault.transfer_logging, NULL,0}, + {"transfer table name",P_STRING,P_LOCAL, &sDefault.transfer_table_name,NULL,0}, {"uid", P_STRING, P_LOCAL, &sDefault.uid, NULL,0}, + {"unique id method", P_STRING, P_LOCAL, &sDefault.unique_id_method, NULL,0}, {"use chroot", P_BOOL, P_LOCAL, &sDefault.use_chroot, NULL,0}, {"write only", P_BOOL, P_LOCAL, &sDefault.write_only, NULL,0}, {NULL, P_BOOL, P_NONE, NULL, NULL,0} @@ -389,9 +428,16 @@ FN_GLOBAL_INTEGER(lp_rsync_port, &Global FN_LOCAL_STRING(lp_auth_users, auth_users) FN_LOCAL_STRING(lp_comment, comment) +FN_LOCAL_STRING(lp_custom_unique_id_select,custom_unique_id_select) +FN_LOCAL_STRING(lp_database_datasource, database_datasource) +FN_LOCAL_STRING(lp_database_password, database_password) +FN_LOCAL_STRING(lp_database_username, database_username) +FN_LOCAL_STRING(lp_delete_table_name,delete_table_name) FN_LOCAL_STRING(lp_dont_compress, dont_compress) +FN_LOCAL_STRING(lp_error_table_name,error_table_name) FN_LOCAL_STRING(lp_exclude, exclude) FN_LOCAL_STRING(lp_exclude_from, exclude_from) +FN_LOCAL_STRING(lp_exit_table_name, exit_table_name) FN_LOCAL_STRING(lp_filter, filter) FN_LOCAL_STRING(lp_gid, gid) FN_LOCAL_STRING(lp_hosts_allow, hosts_allow) @@ -409,15 +455,21 @@ FN_LOCAL_STRING(lp_postxfer_exec, postxf FN_LOCAL_STRING(lp_prexfer_exec, prexfer_exec) FN_LOCAL_STRING(lp_refuse_options, refuse_options) FN_LOCAL_STRING(lp_secrets_file, secrets_file) +FN_LOCAL_STRING(lp_sequence_name,sequence_name) +FN_LOCAL_STRING(lp_session_table_name,session_table_name) FN_LOCAL_INTEGER(lp_syslog_facility, syslog_facility) FN_LOCAL_STRING(lp_temp_dir, temp_dir) +FN_LOCAL_STRING(lp_transfer_table_name, transfer_table_name) FN_LOCAL_STRING(lp_uid, uid) +FN_LOCAL_STRING(lp_unique_id_method,unique_id_method) FN_LOCAL_INTEGER(lp_max_connections, max_connections) FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity) FN_LOCAL_INTEGER(lp_timeout, timeout) +FN_LOCAL_BOOL(lp_database_logging, database_logging) FN_LOCAL_BOOL(lp_fake_super, fake_super) +FN_LOCAL_BOOL(lp_get_custom_id_before_insert,get_custom_id_before_insert) FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors) FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable) FN_LOCAL_BOOL(lp_list, list) --- old/log.c +++ new/log.c @@ -96,7 +96,7 @@ struct { /* * Map from rsync error code to name, or return NULL. */ -static char const *rerr_name(int code) +char const *rerr_name(int code) { int i; for (i = 0; rerr_names[i].name; i++) { --- old/receiver.c +++ new/receiver.c @@ -111,6 +111,10 @@ int get_tmpname(char *fnametmp, char *fn if (maxname < 1) { rprintf(FERROR, "temporary filename too long: %s\n", fname); +#ifdef HAVE_LIBODBC + db_log_error(FERROR,13, "temporary filename too long: %s\n", + fname); +#endif fnametmp[0] = '\0'; return 0; } @@ -176,6 +180,10 @@ static int receive_data(int f_in, char * if (fd != -1 && (j = do_lseek(fd, offset, SEEK_SET)) != offset) { rsyserr(FERROR, errno, "lseek of %s returned %.0f, not %.0f", full_fname(fname), (double)j, (double)offset); +#ifdef HAVE_LIBODBC + db_log_error(FERROR, 14, "lseek failed on %s", + full_fname(fname)); +#endif exit_cleanup(RERR_FILEIO); } } @@ -233,6 +241,11 @@ static int receive_data(int f_in, char * "lseek of %s returned %.0f, not %.0f", full_fname(fname), (double)pos, (double)offset); +#ifdef HAVE_LIBODBC + db_log_error(FERROR, 14, + "lseek failed on %s", + full_fname(fname)); +#endif exit_cleanup(RERR_FILEIO); } continue; @@ -258,6 +271,9 @@ static int receive_data(int f_in, char * report_write_error: rsyserr(FERROR, errno, "write failed on %s", full_fname(fname)); +#ifdef HAVE_LIBODBC + db_log_error(FERROR, 15, "write failed on %s",full_fname(fname)); +#endif exit_cleanup(RERR_FILEIO); } @@ -301,6 +317,12 @@ static void handle_delayed_updates(char rsyserr(FERROR, errno, "rename failed for %s (from %s)", full_fname(fname), partialptr); +#ifdef HAVE_LIBODBC + db_log_error(FERROR, 16, + "rename failed for %s (from %s)", + full_fname(fname), + partialptr); +#endif } else { if (remove_source_files || (preserve_hard_links && F_IS_HLINKED(file))) @@ -454,6 +476,9 @@ int recv_files(int f_in, char *local_nam if (server_filter_list.head && check_filter(&server_filter_list, fname, 0) < 0) { rprintf(FERROR, "attempt to hack rsync failed.\n"); +#ifdef HAVE_LIBODBC + db_log_error(FERROR,17,"attempt to hack rsync failed."); +#endif exit_cleanup(RERR_PROTOCOL); } @@ -512,6 +537,11 @@ int recv_files(int f_in, char *local_nam rprintf(FERROR, "invalid basis_dir index: %d.\n", fnamecmp_type); +#ifdef HAVE_LIBODBC + db_log_error(FERROR, 18, + "invalid basis_dir index: %d.\n", + fnamecmp_type); +#endif exit_cleanup(RERR_PROTOCOL); } pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, @@ -560,6 +590,9 @@ int recv_files(int f_in, char *local_nam } else if (do_fstat(fd1,&st) != 0) { rsyserr(FERROR, errno, "fstat %s failed", full_fname(fnamecmp)); +#ifdef HAVE_LIBODBC + db_log_error(FERROR, 19,"fstat %s failed",full_fname(fnamecmp)); +#endif discard_receive_data(f_in, F_LENGTH(file)); close(fd1); if (inc_recurse) @@ -575,6 +608,9 @@ int recv_files(int f_in, char *local_nam */ rprintf(FERROR,"recv_files: %s is a directory\n", full_fname(fnamecmp)); +#ifdef HAVE_LIBODBC + db_log_error(FERROR,20,"recv_files: %s is a directory",full_fname(fnamecmp)); +#endif discard_receive_data(f_in, F_LENGTH(file)); close(fd1); if (inc_recurse) @@ -609,6 +645,9 @@ int recv_files(int f_in, char *local_nam if (fd2 == -1) { rsyserr(FERROR, errno, "open %s failed", full_fname(fname)); +#ifdef HAVE_LIBODBC + db_log_error(FERROR,22, "open %s failed", full_fname(fname)); +#endif discard_receive_data(f_in, F_LENGTH(file)); if (fd1 != -1) close(fd1); @@ -646,6 +685,10 @@ int recv_files(int f_in, char *local_nam if (fd2 == -1) { rsyserr(FERROR, errno, "mkstemp %s failed", full_fname(fnametmp)); +#ifdef HAVE_LIBODBC + db_log_error(FERROR, 22, "mkstemp %s failed", + full_fname(fnametmp)); +#endif discard_receive_data(f_in, F_LENGTH(file)); if (fd1 != -1) close(fd1); @@ -668,12 +711,19 @@ int recv_files(int f_in, char *local_nam fname, fd2, F_LENGTH(file)); log_item(log_code, file, &initial_stats, iflags, NULL); +#ifdef HAVE_LIBODBC + db_log_transfer(file, &initial_stats, "receive"); +#endif if (fd1 != -1) close(fd1); if (close(fd2) < 0) { rsyserr(FERROR, errno, "close failed on %s", full_fname(fnametmp)); +#ifdef HAVE_LIBODBC + db_log_error(FERROR, 23, "close failed on %s", + full_fname(fnametmp)); +#endif exit_cleanup(RERR_FILEIO); } @@ -730,6 +780,12 @@ int recv_files(int f_in, char *local_nam rprintf(msgtype, "%s: %s failed verification -- update %s%s.\n", errstr, fname, keptstr, redostr); +#ifdef HAVE_LIBODBC + db_log_error(msgtype,24, + "%s: %s failed verification -- update %s%s.\n", + errstr, fname, + keptstr, redostr); +#endif } if (!redoing) { send_msg_int(MSG_REDO, ndx); --- old/sender.c +++ new/sender.c @@ -344,6 +344,9 @@ void send_files(int f_in, int f_out) end_progress(st.st_size); log_item(log_code, file, &initial_stats, iflags, NULL); +#ifdef HAVE_LIBODBC + db_log_transfer(file, &initial_stats,"send"); +#endif if (mbuf) { j = unmap_file(mbuf);