The patches for 3.0.1pre3.
[rsync/rsync-patches.git] / db.diff
CommitLineData
cbdf862c
WD
1Added some DB-access routines to help rsync keep extra filesystem info
2about the files it is dealing with. This adds both the --db=CONFIG_FILE
3option and the "db config" daemon parameter.
4
5For the moment this only adds checksum caching when the --checksum option
6is used. Future improvments may include:
7
8 - Updating of MD5 checksums when transferring any file, even w/o -c.
9
10 - Caching of path info that allows for the finding of files to use for
11 moving/linking/copying/alternate-basis-use.
12
13 - Extend DB support beyond MySQL and SQLite (PostgreSQL?).
14
15To use this patch, run these commands for a successful build:
16
17 patch -p1 <patches/remote-option.diff
18 patch -p1 <patches/db.diff
19 ./configure (optional if already run)
20 make
21
22diff --git a/Makefile.in b/Makefile.in
23--- a/Makefile.in
24+++ b/Makefile.in
25@@ -35,7 +35,7 @@ ZLIBOBJ=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
26 OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
27 util.o main.o checksum.o match.o syscall.o log.o backup.o
28 OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o \
29- fileio.o batch.o clientname.o chmod.o acls.o xattrs.o
30+ fileio.o batch.o clientname.o chmod.o db.o acls.o xattrs.o
31 OBJS3=progress.o pipe.o
32 DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o
33 popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
34diff --git a/checksum.c b/checksum.c
35--- a/checksum.c
36+++ b/checksum.c
37@@ -23,6 +23,7 @@
38
39 extern int checksum_seed;
40 extern int protocol_version;
41+extern int use_db;
42
43 int csum_length = SHORT_SUM_LENGTH; /* initial value */
44
45@@ -100,10 +101,10 @@ void get_checksum2(char *buf, int32 len, char *sum)
46 }
47 }
48
49-void file_checksum(char *fname, char *sum, OFF_T size)
50+void file_checksum(const char *fname, STRUCT_STAT *st_p, char *sum)
51 {
52 struct map_struct *buf;
53- OFF_T i, len = size;
54+ OFF_T i, len = st_p->st_size;
55 md_context m;
56 int32 remainder;
57 int fd;
58@@ -114,7 +115,7 @@ void file_checksum(char *fname, char *sum, OFF_T size)
59 if (fd == -1)
60 return;
61
62- buf = map_file(fd, size, MAX_MAP_SIZE, CSUM_CHUNK);
63+ buf = map_file(fd, len, MAX_MAP_SIZE, CSUM_CHUNK);
64
65 if (protocol_version >= 30) {
66 md5_begin(&m);
67@@ -148,6 +149,9 @@ void file_checksum(char *fname, char *sum, OFF_T size)
68 mdfour_result(&m, (uchar *)sum);
69 }
70
71+ if (use_db)
72+ db_set_checksum(fname, st_p, sum);
73+
74 close(fd);
75 unmap_file(buf);
76 }
77diff --git a/cleanup.c b/cleanup.c
78--- a/cleanup.c
79+++ b/cleanup.c
80@@ -27,6 +27,7 @@ extern int am_daemon;
81 extern int io_error;
82 extern int keep_partial;
83 extern int got_xfer_error;
84+extern int use_db;
85 extern char *partial_dir;
86 extern char *logfile_name;
87
88@@ -124,6 +125,12 @@ NORETURN void _exit_cleanup(int code, const char *file, int line)
89 /* FALLTHROUGH */
90 #include "case_N.h"
91
92+ if (use_db)
93+ db_disconnect();
94+
95+ /* FALLTHROUGH */
96+#include "case_N.h"
97+
98 if (cleanup_child_pid != -1) {
99 int status;
100 int pid = wait_process(cleanup_child_pid, &status, WNOHANG);
101diff --git a/clientserver.c b/clientserver.c
102--- a/clientserver.c
103+++ b/clientserver.c
104@@ -42,6 +42,7 @@ extern int numeric_ids;
105 extern int filesfrom_fd;
106 extern int remote_protocol;
107 extern int protocol_version;
108+extern int always_checksum;
109 extern int io_timeout;
110 extern int no_detach;
111 extern int write_batch;
112@@ -49,6 +50,7 @@ extern int default_af_hint;
113 extern int logfile_format_has_i;
114 extern int logfile_format_has_o_or_i;
115 extern mode_t orig_umask;
116+extern char *db_config;
117 extern char *bind_address;
118 extern char *sockopts;
119 extern char *config_file;
120@@ -782,6 +784,12 @@ static int rsync_module(int f_in, int f_out, int i, char *addr, char *host)
121 } else if (am_root < 0) /* Treat --fake-super from client as --super. */
122 am_root = 2;
123
124+ db_config = lp_db_config(i);
125+ if (!*db_config || (!always_checksum && protocol_version < 30))
126+ db_config = NULL;
127+ else
128+ db_read_config(FLOG, db_config);
129+
130 if (filesfrom_fd == 0)
131 filesfrom_fd = f_in;
132
133diff --git a/configure.in b/configure.in
134--- a/configure.in
135+++ b/configure.in
136@@ -969,6 +969,8 @@ if test x"$enable_acl_support" = x"no" -o x"$enable_xattr_support" = x"no" -o x"
137 fi
138 fi
139
140+LIBS="$LIBS -lmysqlclient -lsqlite3"
141+
142 case "$CC" in
143 ' checker'*|checker*)
144 AC_DEFINE(FORCE_FD_ZERO_MEMSET, 1, [Used to make "checker" understand that FD_ZERO() clears memory.])
145diff --git a/db.c b/db.c
146new file mode 100644
147--- /dev/null
148+++ b/db.c
149@@ -0,0 +1,557 @@
150+/*
151+ * Routines to access extended file info via DB.
152+ *
153+ * Copyright (C) 2008 Wayne Davison
154+ *
155+ * This program is free software; you can redistribute it and/or modify
156+ * it under the terms of the GNU General Public License as published by
157+ * the Free Software Foundation; either version 3 of the License, or
158+ * (at your option) any later version.
159+ *
160+ * This program is distributed in the hope that it will be useful,
161+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
162+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
163+ * GNU General Public License for more details.
164+ *
165+ * You should have received a copy of the GNU General Public License along
166+ * with this program; if not, visit the http://fsf.org website.
167+ */
168+
169+#include "rsync.h"
170+#include "ifuncs.h"
171+
172+#define USE_MYSQL
173+#define USE_SQLITE
174+
175+#ifdef USE_MYSQL
176+#include <mysql/mysql.h>
177+#include <mysql/errmsg.h>
178+#endif
179+#ifdef USE_SQLITE
180+#include <sqlite3.h>
181+#endif
182+
183+extern int protocol_version;
184+extern int checksum_len;
185+
186+#define DB_TYPE_NONE 0
187+#define DB_TYPE_MYSQL 1
188+#define DB_TYPE_SQLITE 2
189+
190+int use_db = DB_TYPE_NONE;
191+
192+static const char *dbhost = NULL, *dbuser = NULL, *dbpass = NULL, *dbname = NULL;
193+static unsigned int dbport = 0;
194+
195+static union {
196+#ifdef USE_MYSQL
197+ MYSQL *mysql;
198+#endif
199+#ifdef USE_SQLITE
200+ sqlite3 *sqlite;
201+#endif
202+ void *all;
203+} dbh;
204+
205+#define SEL_DEV 0
206+#define SEL_SUM 1
207+#define REP_SUM 2
208+#define MAX_PREP_CNT 3
209+
210+static union {
211+#ifdef USE_MYSQL
212+ MYSQL_STMT *mysql;
213+#endif
214+#ifdef USE_SQLITE
215+ sqlite3_stmt *sqlite;
216+#endif
217+ void *all;
218+} statements[MAX_PREP_CNT];
219+
220+static int md_num;
221+static enum logcode log_code;
222+
223+static unsigned int bind_disk_id;
224+static unsigned long long bind_devno, bind_ino, bind_size, bind_mtime, bind_ctime;
225+static char bind_thishost[256], bind_sum[MAX_DIGEST_LEN];
226+static int bind_thishost_len;
227+
228+static unsigned int prior_disk_id = 0;
229+static unsigned long long prior_devno = 0;
230+
231+int db_read_config(enum logcode code, const char *config_file)
232+{
233+ char buf[2048], *cp;
234+ FILE *fp;
235+ int lineno = 0;
236+
237+ log_code = code;
238+
239+ bind_thishost_len = strlcpy(bind_thishost, "localhost", sizeof bind_thishost);
240+
241+ if (!(fp = fopen(config_file, "r"))) {
242+ rsyserr(log_code, errno, "unable to open %s", config_file);
243+ return 0;
244+ }
245+ while (fgets(buf, sizeof buf, fp)) {
246+ lineno++;
247+ if ((cp = strchr(buf, '#')) == NULL
248+ && (cp = strchr(buf, '\r')) == NULL
249+ && (cp = strchr(buf, '\n')) == NULL)
250+ cp = buf + strlen(buf);
251+ while (cp != buf && isSpace(cp-1)) cp--;
252+ *cp = '\0';
253+
254+ if (!*buf)
255+ continue;
256+
257+ if (!(cp = strchr(buf, ':')))
258+ goto invalid_line;
259+ *cp++ = '\0';
260+
261+ while (isSpace(cp)) cp++;
262+ if (strcasecmp(buf, "dbhost") == 0)
263+ dbhost = strdup(cp);
264+ else if (strcasecmp(buf, "dbuser") == 0)
265+ dbuser = strdup(cp);
266+ else if (strcasecmp(buf, "dbpass") == 0)
267+ dbpass = strdup(cp);
268+ else if (strcasecmp(buf, "dbname") == 0)
269+ dbname = strdup(cp);
270+ else if (strcasecmp(buf, "dbport") == 0)
271+ dbport = atoi(cp);
272+ else if (strcasecmp(buf, "thishost") == 0)
273+ bind_thishost_len = strlcpy(bind_thishost, cp, sizeof bind_thishost);
274+ else if (strcasecmp(buf, "dbtype") == 0) {
275+#ifdef USE_MYSQL
276+ if (strcasecmp(cp, "mysql") == 0) {
277+ use_db = DB_TYPE_MYSQL;
278+ continue;
279+ }
280+#endif
281+#ifdef USE_SQLITE
282+ if (strcasecmp(cp, "sqlite") == 0) {
283+ use_db = DB_TYPE_SQLITE;
284+ continue;
285+ }
286+#endif
287+ rprintf(log_code,
288+ "Unsupported dbtype on line #%d in %s.\n",
289+ lineno, config_file);
290+ use_db = DB_TYPE_NONE;
291+ return 0;
292+ } else {
293+ invalid_line:
294+ rprintf(log_code, "Invalid line #%d in %s\n",
295+ lineno, config_file);
296+ use_db = DB_TYPE_NONE;
297+ return 0;
298+ }
299+ }
300+ fclose(fp);
301+
302+ if (bind_thishost_len >= (int)sizeof bind_thishost)
303+ bind_thishost_len = sizeof bind_thishost - 1;
304+
305+ if (!use_db || !dbname) {
306+ rprintf(log_code, "Please specify at least dbtype and dbname in %s.\n", config_file);
307+ use_db = DB_TYPE_NONE;
308+ return 0;
309+ }
310+
311+ md_num = protocol_version >= 30 ? 5 : 4;
312+
313+ return 1;
314+}
315+
316+#ifdef USE_MYSQL
317+static MYSQL_STMT *prepare_mysql(MYSQL_BIND *binds, int bind_cnt, const char *fmt, ...)
318+{
319+ va_list ap;
320+ char *query;
321+ int qlen, param_cnt;
322+ MYSQL_STMT *stmt = mysql_stmt_init(dbh.mysql);
323+
324+ if (stmt == NULL)
325+ out_of_memory("prepare_mysql");
326+
327+ va_start(ap, fmt);
328+ qlen = vasprintf(&query, fmt, ap);
329+ va_end(ap);
330+ if (qlen < 0)
331+ out_of_memory("prepare_mysql");
332+
333+ if (mysql_stmt_prepare(stmt, query, qlen) != 0) {
334+ rprintf(log_code, "Prepare failed: %s\n", mysql_stmt_error(stmt));
335+ return NULL;
336+ }
337+ free(query);
338+
339+ if ((param_cnt = mysql_stmt_param_count(stmt)) != bind_cnt) {
340+ rprintf(log_code, "Parameters in statement = %d, bind vars = %d\n",
341+ param_cnt, bind_cnt);
342+ return NULL;
343+ }
344+ if (bind_cnt)
345+ mysql_stmt_bind_param(stmt, binds);
346+
347+ return stmt;
348+}
349+#endif
350+
351+#ifdef USE_MYSQL
352+static int db_connect_mysql(void)
353+{
354+ MYSQL_BIND binds[10];
355+
356+ if (!(dbh.mysql = mysql_init(NULL)))
357+ out_of_memory("db_read_config");
358+
359+ if (!mysql_real_connect(dbh.mysql, dbhost, dbuser, dbpass, dbname, dbport, NULL, 0))
360+ return 0;
361+
362+ memset(binds, 0, sizeof binds);
363+ binds[0].buffer_type = MYSQL_TYPE_LONGLONG;
364+ binds[0].buffer = &bind_devno;
365+ binds[1].buffer_type = MYSQL_TYPE_STRING;
366+ binds[1].buffer = &bind_thishost;
367+ binds[1].buffer_length = bind_thishost_len;
368+ statements[SEL_DEV].mysql = prepare_mysql(binds, 2,
369+ "SELECT disk_id"
370+ " FROM disk"
371+ " WHERE devno = ? AND host = ? AND mounted = 1");
372+ if (!statements[SEL_DEV].mysql)
373+ return 0;
374+
375+ memset(binds, 0, sizeof binds);
376+ binds[0].buffer_type = MYSQL_TYPE_LONG;
377+ binds[0].buffer = &bind_disk_id;
378+ binds[1].buffer_type = MYSQL_TYPE_LONGLONG;
379+ binds[1].buffer = &bind_ino;
380+ binds[2].buffer_type = MYSQL_TYPE_LONGLONG;
381+ binds[2].buffer = &bind_size;
382+ binds[3].buffer_type = MYSQL_TYPE_LONGLONG;
383+ binds[3].buffer = &bind_mtime;
384+ binds[4].buffer_type = MYSQL_TYPE_LONGLONG;
385+ binds[4].buffer = &bind_ctime;
386+ statements[SEL_SUM].mysql = prepare_mysql(binds, 5,
387+ "SELECT checksum"
388+ " FROM inode_map"
389+ " WHERE disk_id = ? AND ino = ? AND sum_type = %d"
390+ " AND size = ? AND mtime = ? AND ctime = ?",
391+ md_num);
392+ if (!statements[SEL_SUM].mysql)
393+ return 0;
394+
395+ memset(binds, 0, sizeof binds);
396+ binds[0].buffer_type = MYSQL_TYPE_LONG;
397+ binds[0].buffer = &bind_disk_id;
398+ binds[1].buffer_type = MYSQL_TYPE_LONGLONG;
399+ binds[1].buffer = &bind_ino;
400+ binds[2].buffer_type = binds[6].buffer_type = MYSQL_TYPE_LONGLONG;
401+ binds[2].buffer = binds[6].buffer = &bind_size;
402+ binds[3].buffer_type = binds[7].buffer_type = MYSQL_TYPE_LONGLONG;
403+ binds[3].buffer = binds[7].buffer = &bind_mtime;
404+ binds[4].buffer_type = binds[8].buffer_type = MYSQL_TYPE_LONGLONG;
405+ binds[4].buffer = binds[8].buffer = &bind_ctime;
406+ binds[5].buffer_type = binds[9].buffer_type = MYSQL_TYPE_BLOB;
407+ binds[5].buffer = binds[9].buffer = &bind_sum;
408+ binds[5].buffer_length = binds[9].buffer_length = checksum_len;
409+ statements[REP_SUM].mysql = prepare_mysql(binds, 10,
410+ "INSERT INTO inode_map"
411+ " SET disk_id = ?, ino = ?, sum_type = %d,"
412+ " size = ?, mtime = ?, ctime = ?, checksum = ?"
413+ " ON DUPLICATE KEY"
414+ " UPDATE size = ?, mtime = ?, ctime = ?, checksum = ?",
415+ md_num, md_num);
416+ if (!statements[REP_SUM].mysql)
417+ return 0;
418+
419+ return 1;
420+}
421+#endif
422+
423+#ifdef USE_SQLITE
424+static int db_connect_sqlite(void)
425+{
426+ char *sql;
427+
428+#if 0
429+ if (sqlite3_open_v2(dbname, &dbh.sqlite, SQLITE_OPEN_READWRITE, NULL) != 0)
430+ return 0;
431+#else
432+ if (sqlite3_open(dbname, &dbh.sqlite) != 0)
433+ return 0;
434+#endif
435+
436+ sql = "SELECT disk_id"
437+ " FROM disk"
438+ " WHERE devno = ? AND host = ? AND mounted = 1";
439+ if (sqlite3_prepare_v2(dbh.sqlite, sql, -1, &statements[SEL_DEV].sqlite, NULL) != 0)
440+ return 0;
441+
442+ if (asprintf(&sql,
443+ "SELECT checksum"
444+ " FROM inode_map"
445+ " WHERE disk_id = ? AND ino = ? AND sum_type = %d"
446+ " AND size = ? AND mtime = ? AND ctime = ?",
447+ md_num) < 0
448+ || sqlite3_prepare_v2(dbh.sqlite, sql, -1, &statements[SEL_SUM].sqlite, NULL) != 0)
449+ return 0;
450+ free(sql);
451+
452+ if (asprintf(&sql,
453+ "INSERT OR REPLACE INTO inode_map"
454+ " (disk_id, ino, sum_type, size, mtime, ctime, checksum)"
455+ " VALUES(?, ?, %d, ?, ?, ?, ?)",
456+ md_num) < 0
457+ || sqlite3_prepare_v2(dbh.sqlite, sql, -1, &statements[REP_SUM].sqlite, NULL) != 0)
458+ return 0;
459+ free(sql);
460+
461+ return 1;
462+}
463+#endif
464+
465+int db_connect(void)
466+{
467+ switch (use_db) {
468+#ifdef USE_MYSQL
469+ case DB_TYPE_MYSQL:
470+ if (db_connect_mysql())
471+ return 1;
472+ break;
473+#endif
474+#ifdef USE_SQLITE
475+ case DB_TYPE_SQLITE:
476+ if (db_connect_sqlite())
477+ return 1;
478+ break;
479+#endif
480+ }
481+
482+ rprintf(log_code, "Unable to connect to DB\n");
483+ db_disconnect();
484+ use_db = DB_TYPE_NONE;
485+
486+ return 0;
487+}
488+
489+void db_disconnect(void)
490+{
491+ int ndx;
492+
493+ if (!dbh.all)
494+ return;
495+
496+ for (ndx = 0; ndx < MAX_PREP_CNT; ndx++) {
497+ if (statements[ndx].all) {
498+ switch (use_db) {
499+#ifdef USE_MYSQL
500+ case DB_TYPE_MYSQL:
501+ mysql_stmt_close(statements[ndx].mysql);
502+ break;
503+#endif
504+#ifdef USE_SQLITE
505+ case DB_TYPE_SQLITE:
506+ sqlite3_finalize(statements[ndx].sqlite);
507+ break;
508+#endif
509+ }
510+ statements[ndx].all = NULL;
511+ }
512+ }
513+
514+ switch (use_db) {
515+#ifdef USE_MYSQL
516+ case DB_TYPE_MYSQL:
517+ mysql_close(dbh.mysql);
518+ break;
519+#endif
520+#ifdef USE_SQLITE
521+ case DB_TYPE_SQLITE:
522+ sqlite3_close(dbh.sqlite);
523+ break;
524+#endif
525+ }
526+
527+ dbh.all = NULL;
528+}
529+
530+static MYSQL_STMT *exec_mysql(int ndx)
531+{
532+ MYSQL_STMT *stmt = statements[ndx].mysql;
533+ int rc;
534+
535+ if ((rc = mysql_stmt_execute(stmt)) == CR_SERVER_LOST) {
536+ db_disconnect();
537+ if (db_connect()) {
538+ stmt = statements[ndx].mysql;
539+ rc = mysql_stmt_execute(stmt);
540+ }
541+ }
542+ if (rc != 0) {
543+ rprintf(log_code, "SQL execute failed: %s\n", mysql_stmt_error(stmt));
544+ return NULL;
545+ }
546+
547+ return stmt;
548+}
549+
550+static int fetch_mysql(MYSQL_BIND *binds, int bind_cnt, int ndx)
551+{
552+ unsigned long length[32];
553+ my_bool is_null[32], error[32];
554+ MYSQL_STMT *stmt;
555+ int i, rc;
556+
557+ if (bind_cnt > 32)
558+ exit_cleanup(RERR_UNSUPPORTED);
559+
560+ if ((stmt = exec_mysql(ndx)) == NULL)
561+ return 0;
562+
563+ for (i = 0; i < bind_cnt; i++) {
564+ binds[i].is_null = &is_null[i];
565+ binds[i].length = &length[i];
566+ binds[i].error = &error[i];
567+ }
568+ mysql_stmt_bind_result(stmt, binds);
569+
570+ if ((rc = mysql_stmt_fetch(stmt)) != 0) {
571+ if (rc != MYSQL_NO_DATA) {
572+ rprintf(log_code, "SELECT fetch failed: %s\n",
573+ mysql_stmt_error(stmt));
574+ }
575+ mysql_stmt_free_result(stmt);
576+ return 0;
577+ }
578+
579+ mysql_stmt_free_result(stmt);
580+
581+ return is_null[0] ? 0 : 1;
582+}
583+
584+static void get_disk_id(unsigned long long devno)
585+{
586+ switch (use_db) {
587+#ifdef USE_MYSQL
588+ case DB_TYPE_MYSQL: {
589+ MYSQL_BIND binds[1];
590+
591+ bind_devno = devno; /* The one variable SEL_DEV input value. */
592+
593+ /* Bind where to put the output. */
594+ binds[0].buffer_type = MYSQL_TYPE_LONG;
595+ binds[0].buffer = &prior_disk_id;
596+ if (!fetch_mysql(binds, 1, SEL_DEV))
597+ prior_disk_id = 0;
598+ break;
599+ }
600+#endif
601+#ifdef USE_SQLITE
602+ case DB_TYPE_SQLITE: {
603+ sqlite3_stmt *stmt = statements[SEL_DEV].sqlite;
604+ sqlite3_bind_int64(stmt, 1, devno);
605+ sqlite3_bind_text(stmt, 2, bind_thishost, bind_thishost_len, SQLITE_STATIC);
606+ if (sqlite3_step(stmt) == SQLITE_ROW)
607+ prior_disk_id = sqlite3_column_int(stmt, 0);
608+ else
609+ prior_disk_id = 0;
610+ sqlite3_reset(stmt);
611+ break;
612+ }
613+#endif
614+ }
615+
616+ prior_devno = devno;
617+}
618+
619+int db_get_checksum(UNUSED(const char *fname), const STRUCT_STAT *st_p, char *sum)
620+{
621+ if (prior_devno != st_p->st_dev)
622+ get_disk_id(st_p->st_dev);
623+ if (prior_disk_id == 0)
624+ return 0;
625+
626+ switch (use_db) {
627+#ifdef USE_MYSQL
628+ case DB_TYPE_MYSQL: {
629+ MYSQL_BIND binds[1];
630+
631+ bind_disk_id = prior_disk_id;
632+ bind_ino = st_p->st_ino;
633+ bind_size = st_p->st_size;
634+ bind_mtime = st_p->st_mtime;
635+ bind_ctime = st_p->st_ctime;
636+
637+ binds[0].buffer_type = MYSQL_TYPE_BLOB;
638+ binds[0].buffer = sum;
639+ binds[0].buffer_length = checksum_len;
640+ return fetch_mysql(binds, 1, SEL_SUM);
641+ }
642+#endif
643+#ifdef USE_SQLITE
644+ case DB_TYPE_SQLITE: {
645+ sqlite3_stmt *stmt = statements[SEL_SUM].sqlite;
646+ sqlite3_bind_int(stmt, 1, prior_disk_id);
647+ sqlite3_bind_int64(stmt, 2, st_p->st_ino);
648+ sqlite3_bind_int64(stmt, 3, st_p->st_size);
649+ sqlite3_bind_int64(stmt, 4, st_p->st_mtime);
650+ sqlite3_bind_int64(stmt, 5, st_p->st_ctime);
651+ if (sqlite3_step(stmt) == SQLITE_ROW) {
652+ int len = sqlite3_column_bytes(stmt, 0);
653+ if (len > MAX_DIGEST_LEN)
654+ len = MAX_DIGEST_LEN;
655+ memcpy(sum, sqlite3_column_blob(stmt, 0), len);
656+ sqlite3_reset(stmt);
657+ return 1;
658+ }
659+ sqlite3_reset(stmt);
660+ return 0;
661+ }
662+#endif
663+ }
664+
665+ return 0;
666+}
667+
668+int db_set_checksum(UNUSED(const char *fname), const STRUCT_STAT *st_p, const char *sum)
669+{
670+ if (prior_devno != st_p->st_dev)
671+ get_disk_id(st_p->st_dev);
672+ if (prior_disk_id == 0)
673+ return 0;
674+
675+ switch (use_db) {
676+#ifdef USE_MYSQL
677+ case DB_TYPE_MYSQL: {
678+ bind_disk_id = prior_disk_id;
679+ bind_ino = st_p->st_ino;
680+ bind_size = st_p->st_size;
681+ bind_mtime = st_p->st_mtime;
682+ bind_ctime = st_p->st_ctime;
683+ memcpy(bind_sum, sum, checksum_len);
684+
685+ return exec_mysql(REP_SUM) != NULL;
686+ }
687+#endif
688+#ifdef USE_SQLITE
689+ case DB_TYPE_SQLITE: {
690+ int rc;
691+ sqlite3_stmt *stmt = statements[REP_SUM].sqlite;
692+ sqlite3_bind_int(stmt, 1, prior_disk_id);
693+ sqlite3_bind_int64(stmt, 2, st_p->st_ino);
694+ sqlite3_bind_int64(stmt, 3, st_p->st_size);
695+ sqlite3_bind_int64(stmt, 4, st_p->st_mtime);
696+ sqlite3_bind_int64(stmt, 5, st_p->st_ctime);
697+ sqlite3_bind_blob(stmt, 6, sum, checksum_len, SQLITE_TRANSIENT);
698+ rc = sqlite3_step(stmt);
699+ sqlite3_reset(stmt);
700+ return rc == SQLITE_DONE;
701+ }
702+#endif
703+ }
704+
705+ return 0;
706+}
707diff --git a/flist.c b/flist.c
708--- a/flist.c
709+++ b/flist.c
710@@ -54,6 +54,7 @@ extern int preserve_devices;
711 extern int preserve_specials;
712 extern int uid_ndx;
713 extern int gid_ndx;
714+extern int use_db;
715 extern int eol_nulls;
716 extern int relative_paths;
717 extern int implied_dirs;
718@@ -1235,14 +1236,16 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
719 memcpy(bp + basename_len, linkname, linkname_len);
720 #endif
721
722- if (always_checksum && am_sender && S_ISREG(st.st_mode))
723- file_checksum(thisname, tmp_sum, st.st_size);
724-
725 if (am_sender)
726 F_PATHNAME(file) = pathname;
727 else if (!pool)
728 F_DEPTH(file) = extra_len / EXTRA_LEN;
729
730+ if (always_checksum && am_sender && S_ISREG(st.st_mode)) {
731+ if (!use_db || !db_get_checksum(thisname, &st, tmp_sum))
732+ file_checksum(thisname, &st, tmp_sum);
733+ }
734+
735 /* This code is only used by the receiver when it is building
736 * a list of files for a delete pass. */
737 if (keep_dirlinks && linkname_len && flist) {
738@@ -1858,6 +1861,9 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
739 | (eol_nulls || reading_remotely ? RL_EOL_NULLS : 0);
740 int implied_dot_dir = 0;
741
742+ if (use_db)
743+ db_connect();
744+
745 rprintf(FLOG, "building file list\n");
746 if (show_filelist_p())
747 start_filelist_progress("building file list");
748diff --git a/generator.c b/generator.c
749--- a/generator.c
750+++ b/generator.c
751@@ -58,6 +58,7 @@ extern int update_only;
752 extern int ignore_existing;
753 extern int ignore_non_existing;
754 extern int inplace;
755+extern int use_db;
756 extern int append_mode;
757 extern int make_backups;
758 extern int csum_length;
759@@ -718,7 +719,8 @@ int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st)
760 of the file time to determine whether to sync */
761 if (always_checksum > 0 && S_ISREG(st->st_mode)) {
762 char sum[MAX_DIGEST_LEN];
763- file_checksum(fn, sum, st->st_size);
764+ if (!use_db || !db_get_checksum(fn, st, sum))
765+ file_checksum(fn, st, sum);
766 return memcmp(sum, F_SUM(file), checksum_len) == 0;
767 }
768
769@@ -2161,6 +2163,9 @@ void generate_files(int f_out, const char *local_name)
770 : "enabled");
771 }
772
773+ if (use_db && always_checksum)
774+ db_connect();
775+
776 /* Since we often fill up the outgoing socket and then just sit around
777 * waiting for the other 2 processes to do their thing, we don't want
778 * to exit on a timeout. If the data stops flowing, the receiver will
779diff --git a/loadparm.c b/loadparm.c
780--- a/loadparm.c
781+++ b/loadparm.c
782@@ -126,6 +126,7 @@ typedef struct
783 char *auth_users;
784 char *charset;
785 char *comment;
786+ char *db_config;
787 char *dont_compress;
788 char *exclude;
789 char *exclude_from;
790@@ -177,6 +178,7 @@ static service sDefault =
791 /* auth_users; */ NULL,
792 /* charset; */ NULL,
793 /* comment; */ NULL,
794+ /* db_config; */ NULL,
795 /* dont_compress; */ DEFAULT_DONT_COMPRESS,
796 /* exclude; */ NULL,
797 /* exclude_from; */ NULL,
798@@ -307,6 +309,7 @@ static struct parm_struct parm_table[] =
799 {"auth users", P_STRING, P_LOCAL, &sDefault.auth_users, NULL,0},
800 {"charset", P_STRING, P_LOCAL, &sDefault.charset, NULL,0},
801 {"comment", P_STRING, P_LOCAL, &sDefault.comment, NULL,0},
802+ {"db config", P_STRING, P_LOCAL, &sDefault.db_config, NULL,0},
803 {"dont compress", P_STRING, P_LOCAL, &sDefault.dont_compress, NULL,0},
804 {"exclude from", P_STRING, P_LOCAL, &sDefault.exclude_from, NULL,0},
805 {"exclude", P_STRING, P_LOCAL, &sDefault.exclude, NULL,0},
806@@ -400,6 +403,7 @@ FN_GLOBAL_INTEGER(lp_rsync_port, &Globals.rsync_port)
807 FN_LOCAL_STRING(lp_auth_users, auth_users)
808 FN_LOCAL_STRING(lp_charset, charset)
809 FN_LOCAL_STRING(lp_comment, comment)
810+FN_LOCAL_STRING(lp_db_config, db_config)
811 FN_LOCAL_STRING(lp_dont_compress, dont_compress)
812 FN_LOCAL_STRING(lp_exclude, exclude)
813 FN_LOCAL_STRING(lp_exclude_from, exclude_from)
814diff --git a/main.c b/main.c
815--- a/main.c
816+++ b/main.c
817@@ -49,6 +49,7 @@ extern int copy_unsafe_links;
818 extern int keep_dirlinks;
819 extern int preserve_hard_links;
820 extern int protocol_version;
821+extern int always_checksum;
822 extern int file_total;
823 extern int recurse;
824 extern int xfer_dirs;
825@@ -73,6 +74,7 @@ extern char *partial_dir;
826 extern char *dest_option;
827 extern char *basis_dir[];
828 extern char *rsync_path;
829+extern char *db_config;
830 extern char *shell_cmd;
831 extern char *batch_name;
832 extern char *password_file;
833@@ -1482,6 +1484,9 @@ int main(int argc,char *argv[])
834 exit_cleanup(RERR_SYNTAX);
835 }
836
837+ if (db_config && (always_checksum || protocol_version >= 30))
838+ db_read_config(FERROR, db_config);
839+
840 if (am_server) {
841 set_nonblocking(STDIN_FILENO);
842 set_nonblocking(STDOUT_FILENO);
843diff --git a/options.c b/options.c
844--- a/options.c
845+++ b/options.c
846@@ -92,6 +92,7 @@ int use_qsort = 0;
847 char *files_from = NULL;
848 int filesfrom_fd = -1;
849 char *filesfrom_host = NULL;
850+char *db_config = NULL;
851 int eol_nulls = 0;
852 int protect_args = 0;
853 int human_readable = 0;
854@@ -321,6 +322,7 @@ void usage(enum logcode F)
855 rprintf(F," -q, --quiet suppress non-error messages\n");
856 rprintf(F," --no-motd suppress daemon-mode MOTD (see manpage caveat)\n");
857 rprintf(F," -c, --checksum skip based on checksum, not mod-time & size\n");
858+ rprintf(F," --db=CONFIG_FILE specify a config file for FS DB\n");
859 rprintf(F," -a, --archive archive mode; equals -rlptgoD (no -H,-A,-X)\n");
860 rprintf(F," --no-OPTION turn off an implied OPTION (e.g. --no-D)\n");
861 rprintf(F," -r, --recursive recurse into directories\n");
862@@ -579,6 +581,7 @@ static struct poptOption long_options[] = {
863 {"checksum", 'c', POPT_ARG_VAL, &always_checksum, 1, 0, 0 },
864 {"no-checksum", 0, POPT_ARG_VAL, &always_checksum, 0, 0, 0 },
865 {"no-c", 0, POPT_ARG_VAL, &always_checksum, 0, 0, 0 },
866+ {"db", 0, POPT_ARG_STRING, &db_config, 0, 0, 0 },
867 {"block-size", 'B', POPT_ARG_LONG, &block_size, 0, 0, 0 },
868 {"compare-dest", 0, POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
869 {"copy-dest", 0, POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
870diff --git a/pipe.c b/pipe.c
871--- a/pipe.c
872+++ b/pipe.c
873@@ -26,6 +26,10 @@ extern int am_sender;
874 extern int am_server;
875 extern int blocking_io;
876 extern int filesfrom_fd;
877+extern int always_checksum;
878+extern int protocol_version;
879+extern int use_db;
880+extern char *db_config;
881 extern mode_t orig_umask;
882 extern char *logfile_name;
883 extern int remote_option_cnt;
884@@ -141,6 +145,9 @@ pid_t local_child(int argc, char **argv, int *f_in, int *f_out,
885 logfile_close();
886 }
887
888+ use_db = 0;
889+ db_config = NULL;
890+
891 if (remote_option_cnt) {
892 int rc = remote_option_cnt + 1;
893 const char **rv = remote_options;
894@@ -148,6 +155,8 @@ pid_t local_child(int argc, char **argv, int *f_in, int *f_out,
895 option_error();
896 exit_cleanup(RERR_SYNTAX);
897 }
898+ if (db_config && (always_checksum || protocol_version >= 30))
899+ db_read_config(FERROR, db_config);
900 }
901
902 if (dup2(to_child_pipe[0], STDIN_FILENO) < 0 ||
903diff --git a/receiver.c b/receiver.c
904--- a/receiver.c
905+++ b/receiver.c
906@@ -43,11 +43,13 @@ extern int basis_dir_cnt;
907 extern int make_backups;
908 extern int cleanup_got_literal;
909 extern int remove_source_files;
910+extern int always_checksum;
911 extern int append_mode;
912 extern int sparse_files;
913 extern int keep_partial;
914 extern int checksum_seed;
915 extern int inplace;
916+extern int use_db;
917 extern int delay_updates;
918 extern mode_t orig_umask;
919 extern struct stats stats;
920@@ -399,6 +401,9 @@ int recv_files(int f_in, char *local_name)
921 if (verbose > 2)
922 rprintf(FINFO, "recv_files(%d) starting\n", cur_flist->used);
923
924+ if (use_db && !always_checksum)
925+ db_connect();
926+
927 if (delay_updates)
928 delayed_bits = bitbag_create(cur_flist->used + 1);
929
930diff --git a/support/dbupdate b/support/dbupdate
931new file mode 100755
932--- /dev/null
933+++ b/support/dbupdate
934@@ -0,0 +1,281 @@
935+#!/usr/bin/perl -w
936+use strict;
937+
938+use DBI;
939+use Getopt::Long;
940+use Cwd qw(abs_path cwd);
941+use Digest::MD4;
942+use Digest::MD5;
943+
944+my $MOUNT_FILE = '/etc/mtab';
945+
946+&Getopt::Long::Configure('bundling');
947+&usage if !&GetOptions(
948+ 'db=s' => \( my $db_config ),
949+ 'mounts|m' => \( my $update_mounts ),
950+ 'recurse|r' => \( my $recurse_opt ),
951+ 'check|c' => \( my $check_opt ),
952+ 'verbose|v+' => \( my $verbosity = 0 ),
953+ 'help|h' => \( my $help_opt ),
954+);
955+&usage if $help_opt || !defined $db_config;
956+
957+my %config;
958+open(IN, '<', $db_config) or die "Unable to open $db_config: $!\n";
959+while (<IN>) {
960+ s/[#\r\n].*//s;
961+ next if /^$/;
962+ my($key, $val) = /^(\S+):\s*(.*)/ or die "Unable to parse line $. of $db_config\n";
963+ $config{$key} = $val;
964+}
965+close IN;
966+
967+die "You must define at least dbtype and dbname in $db_config\n"
968+ unless defined $config{'dbtype'} && defined $config{'dbname'};
969+
970+my $thishost = $config{'thishost'} || 'localhost';
971+
972+my $connect = 'DBI:' . $config{'dbtype'} . ':database=' . $config{'dbname'};
973+$connect =~ s/:database=/:dbname=/ if $config{'dbtype'} eq 'SQLite';
974+$connect .= ';host=' . $config{'dbhost'} if defined $config{'dbhost'};
975+$connect .= ';port=' . $config{'dbport'} if defined $config{'dbport'};
976+
977+my $dbh = DBI->connect($connect, $config{'dbuser'}, $config{'dbpass'})
978+ or die "DB connection failed\n";
979+
980+END {
981+ $dbh->disconnect if defined $dbh;
982+}
983+
984+my $sel_disk_H = $dbh->prepare("
985+ SELECT disk_id, devno, mounted, comment
986+ FROM disk
987+ WHERE host = ?
988+ ") or die $dbh->errstr;
989+
990+my $ins_disk_H = $dbh->prepare("
991+ INSERT INTO disk
992+ (devno, host, mounted, comment)
993+ VALUES(?, ?, ?, ?)
994+ ") or die $dbh->errstr;
995+
996+my $up_disk_H = $dbh->prepare("
997+ UPDATE disk
998+ SET mounted = ?
999+ WHERE disk_id = ?
1000+ ") or die $dbh->errstr;
1001+
1002+my $row_id = $config{'dbtype'} eq 'SQLite' ? 'ROWID' : 'ID';
1003+my $sel_lastid_H = $dbh->prepare("
1004+ SELECT LAST_INSERT_$row_id()
1005+ ") or die $dbh->errstr;
1006+
1007+my $sel_sum_H = $dbh->prepare("
1008+ SELECT sum_type, checksum
1009+ FROM inode_map
1010+ WHERE disk_id = ? AND ino = ? AND size = ? AND mtime = ? AND ctime = ?
1011+ ") or die $dbh->errstr;
1012+
1013+my $rep_sum_H = $dbh->prepare("
1014+ REPLACE INTO inode_map
1015+ (disk_id, ino, size, mtime, ctime, sum_type, checksum)
1016+ VALUES(?, ?, ?, ?, ?, ?, ?)
1017+ ") or die $dbh->errstr;
1018+
1019+my %mounts;
1020+if ($update_mounts) {
1021+ open(IN, $MOUNT_FILE) or die "Unable to open $MOUNT_FILE: $!\n";
1022+ while (<IN>) {
1023+ my($devname, $mnt) = (split)[0,1];
1024+ next unless $devname =~ m#^/dev#;
1025+ my($devno) = (stat($mnt))[0];
1026+ if (!defined $devno) {
1027+ warn "Unable to stat $mnt: $!\n";
1028+ next;
1029+ }
1030+ $mounts{$devno} = "$devname on $mnt";
1031+ }
1032+ close IN;
1033+}
1034+
1035+my %disk_id;
1036+$sel_disk_H->execute($thishost);
1037+while (my($disk_id, $devno, $mounted, $comment) = $sel_disk_H->fetchrow_array) {
1038+ if ($update_mounts) {
1039+ if (defined $mounts{$devno}) {
1040+ if ($comment ne $mounts{$devno}) {
1041+ if ($mounted) {
1042+ $up_disk_H->execute(0, $disk_id);
1043+ }
1044+ next;
1045+ }
1046+ if (!$mounted) {
1047+ $up_disk_H->execute(1, $disk_id);
1048+ }
1049+ } else {
1050+ if ($mounted) {
1051+ $up_disk_H->execute(0, $disk_id);
1052+ }
1053+ next;
1054+ }
1055+ } else {
1056+ next unless $mounted;
1057+ }
1058+ $disk_id{$devno} = $disk_id;
1059+}
1060+$sel_disk_H->finish;
1061+
1062+if ($update_mounts) {
1063+ while (my($devno, $comment) = each %mounts) {
1064+ next if $disk_id{$devno};
1065+ $ins_disk_H->execute($devno, $thishost, 1, $comment);
1066+ $sel_lastid_H->execute;
1067+ ($disk_id{$devno}) = $sel_lastid_H->fetchrow_array;
1068+ $sel_lastid_H->finish;
1069+ }
1070+}
1071+
1072+my $start_dir = cwd();
1073+
1074+my @dirs = @ARGV;
1075+@dirs = '.' unless @dirs;
1076+foreach (@dirs) {
1077+ $_ = abs_path($_);
1078+}
1079+
1080+$| = 1;
1081+
1082+my $exit_code = 0;
1083+
1084+my $md4 = Digest::MD4->new;
1085+my $md5 = Digest::MD5->new;
1086+
1087+while (@dirs) {
1088+ my $dir = shift @dirs;
1089+
1090+ if (!chdir($dir)) {
1091+ warn "Unable to chdir to $dir: $!\n";
1092+ next;
1093+ }
1094+ if (!opendir(DP, '.')) {
1095+ warn "Unable to opendir $dir: $!\n";
1096+ next;
1097+ }
1098+
1099+ my $reldir = $dir;
1100+ $reldir =~ s#^$start_dir(/|$)# $1 ? '' : '.' #eo;
1101+ print "$reldir ... \n" if $verbosity;
1102+
1103+ my @subdirs;
1104+ while (defined(my $fn = readdir(DP))) {
1105+ next if $fn =~ /^\.\.?$/ || -l $fn;
1106+ if (-d _) {
1107+ push(@subdirs, "$dir/$fn") unless $fn =~ /^(CVS|\.svn|\.git|\.bzr)$/;
1108+ next;
1109+ }
1110+ next unless -f _;
1111+
1112+ my($dev,$ino,$size,$mtime,$ctime) = (stat(_))[0,1,7,9,10];
1113+ my $disk_id = $disk_id{$dev} or next;
1114+ $sel_sum_H->execute($disk_id,$ino,$size,$mtime,$ctime) or die $!;
1115+ my($sum4, $dbsum4, $sum5, $dbsum5);
1116+ my $dbsumcnt = 0;
1117+ while (my($sum_type, $checksum) = $sel_sum_H->fetchrow_array) {
1118+ if ($sum_type == 4) {
1119+ $dbsum4 = $checksum;
1120+ $dbsumcnt++;
1121+ } elsif ($sum_type == 5) {
1122+ $dbsum5 = $checksum;
1123+ $dbsumcnt++;
1124+ }
1125+ }
1126+ $sel_sum_H->finish;
1127+
1128+ next if !$check_opt && $dbsumcnt == 2;
1129+
1130+ if (!$check_opt || $dbsumcnt || $verbosity > 2) {
1131+ if (!open(IN, $fn)) {
1132+ print STDERR "Unable to read $fn: $!\n";
1133+ next;
1134+ }
1135+
1136+ while (1) {
1137+ while (sysread(IN, $_, 64*1024)) {
1138+ $md4->add($_);
1139+ $md5->add($_);
1140+ }
1141+ $sum4 = $md4->digest;
1142+ $sum5 = $md5->digest;
1143+ print ' ', unpack('H*', $sum4), ' ', unpack('H*', $sum5) if $verbosity > 2;
1144+ print " $fn" if $verbosity > 1;
1145+ my($ino2,$size2,$mtime2,$ctime2) = (stat(IN))[1,7,9,10];
1146+ last if $ino == $ino2 && $size == $size2 && $mtime == $mtime2 && $ctime == $ctime2;
1147+ $ino = $ino2;
1148+ $size = $size2;
1149+ $mtime = $mtime2;
1150+ $ctime = $ctime2;
1151+ sysseek(IN, 0, 0);
1152+ print " REREADING\n" if $verbosity > 1;
1153+ }
1154+
1155+ close IN;
1156+ } elsif ($verbosity > 1) {
1157+ print "_$fn";
1158+ }
1159+
1160+ if ($check_opt) {
1161+ my $dif;
1162+ if ($dbsumcnt == 0) {
1163+ $dif = ' --MISSING--';
1164+ } else {
1165+ $dif = '';
1166+ if (!defined $dbsum4) {
1167+ $dif .= ' -NO-MD4-';
1168+ } elsif ($sum4 ne $dbsum4) {
1169+ $dif .= ' -MD4-CHANGED-';
1170+ }
1171+ if (!defined $dbsum5) {
1172+ $dif .= ' ---NO-MD5---';
1173+ } elsif ($sum5 ne $dbsum5) {
1174+ $dif .= ' -MD5-CHANGED-';
1175+ }
1176+ if ($dif eq '') {
1177+ print " ====OK====\n" if $verbosity > 1;
1178+ next;
1179+ }
1180+ $dif =~ s/MD4-CHANGED MD5-//;
1181+ }
1182+ if ($verbosity < 2) {
1183+ print $verbosity ? ' ' : "$reldir/";
1184+ print $fn;
1185+ }
1186+ print $dif, "\n";
1187+ $exit_code = 1;
1188+ } else {
1189+ print "\n" if $verbosity > 1;
1190+ $rep_sum_H->execute($disk_id, $ino, $size, $mtime, $ctime, 4, $sum4);
1191+ $rep_sum_H->execute($disk_id, $ino, $size, $mtime, $ctime, 5, $sum5);
1192+ }
1193+ }
1194+
1195+ closedir DP;
1196+
1197+ unshift(@dirs, sort @subdirs) if $recurse_opt;
1198+}
1199+
1200+exit $exit_code;
1201+
1202+sub usage
1203+{
1204+ die <<EOT;
1205+Usage: rsyncsums --db=CONFIG_FILE [OPTIONS] [DIRS]
1206+
1207+Options:
1208+ --db=FILE Specify the config FILE to read for the DB info.
1209+ -m, --mounts Update mount info.
1210+ -r, --recurse Scan files in subdirectories too.
1211+ -c, --check Check if the checksums are right (doesn't update).
1212+ -v, --verbose Mention what we're doing. Repeat for more info.
1213+ -h, --help Display this help message.
1214+EOT
1215+}