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
5Future improvements may include:
6
7 - Updating of MD4 checksums when transferring any file, even w/o -c.
8   To make that work we'd need to make the sender force checksum_seed to
9   0 when using a DB and having the receiving side check to see if it
10   got a 0 checksum_seed.
11
12 - Caching of path info that allows for the finding of files to use for
13   moving/linking/copying/alternate-basis-use.
14
15 - Extend DB support beyond MySQL and SQLite (PostgreSQL?).
16
17To use this patch, run these commands for a successful build:
18
19    patch -p1 <patches/db.diff
20    ./prepare-source
21    ./configure
22    make
23
24based-on: e94bad1c156fc3910f24e2b3b71a81b0b0bdeb70
25diff --git a/.gitignore b/.gitignore
26--- a/.gitignore
27+++ b/.gitignore
28@@ -30,6 +30,7 @@ aclocal.m4
29 /getgroups
30 /gmon.out
31 /rsync
32+/rsyncdb
33 /stunnel-rsyncd.conf
34 /shconfig
35 /git-version.h
36diff --git a/Makefile.in b/Makefile.in
37--- a/Makefile.in
38+++ b/Makefile.in
39@@ -4,6 +4,7 @@ prefix=@prefix@
40 datarootdir=@datarootdir@
41 exec_prefix=@exec_prefix@
42 bindir=@bindir@
43+sbindir=@sbindir@
44 libdir=@libdir@/rsync
45 mandir=@mandir@
46
47@@ -33,7 +34,7 @@ SIMD_x86_64=simd-checksum-x86_64.o
48 ASM_x86_64=lib/md5-asm-x86_64.o
49
50 GENFILES=configure.sh aclocal.m4 config.h.in rsync.1 rsync.1.html \
51-	 rsync-ssl.1 rsync-ssl.1.html rsyncd.conf.5 rsyncd.conf.5.html
52+	 rsync-ssl.1 rsync-ssl.1.html rsyncd.conf.5 rsyncd.conf.5.html rsyncdb.1 rsyncdb.1.html
53 HEADERS=byteorder.h config.h errcode.h proto.h rsync.h ifuncs.h itypes.h inums.h \
54 	lib/pool_alloc.h lib/mdigest.h lib/md-defines.h version.h
55 LIBOBJ=lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o lib/md5.o \
56@@ -43,7 +44,7 @@ zlib_OBJS=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
57 OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
58 	util.o util2.o main.o checksum.o match.o syscall.o log.o backup.o delete.o
59 OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o \
60-	usage.o fileio.o batch.o clientname.o chmod.o acls.o xattrs.o
61+	usage.o fileio.o batch.o clientname.o chmod.o db.o acls.o xattrs.o
62 OBJS3=progress.o pipe.o @ASM@
63 DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o
64 popt_OBJS=popt/findme.o  popt/popt.o  popt/poptconfig.o \
65@@ -75,10 +76,12 @@ install: all
66 	-$(MKDIR_P) $(DESTDIR)$(bindir)
67 	$(INSTALLCMD) $(INSTALL_STRIP) -m 755 rsync$(EXEEXT) $(DESTDIR)$(bindir)
68 	$(INSTALLCMD) -m 755 $(srcdir)/rsync-ssl $(DESTDIR)$(bindir)
69+	rsync -ilt rsyncdb$(EXEEXT) $(DESTDIR)$(bindir)/
70 	-$(MKDIR_P) $(DESTDIR)$(mandir)/man1
71 	-$(MKDIR_P) $(DESTDIR)$(mandir)/man5
72 	if test -f rsync.1; then $(INSTALLMAN) -m 644 rsync.1 $(DESTDIR)$(mandir)/man1; fi
73 	if test -f rsync-ssl.1; then $(INSTALLMAN) -m 644 rsync-ssl.1 $(DESTDIR)$(mandir)/man1; fi
74+	if test -f rsyncdb.1; then $(INSTALLMAN) -m 644 rsyncdb.1 $(DESTDIR)$(mandir)/man1; fi
75 	if test -f rsyncd.conf.5; then $(INSTALLMAN) -m 644 rsyncd.conf.5 $(DESTDIR)$(mandir)/man5; fi
76
77 install-ssl-daemon: stunnel-rsyncd.conf
78@@ -96,10 +99,13 @@ install-strip:
79 rsync$(EXEEXT): $(OBJS)
80 	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
81
82+rsyncdb$(EXEEXT): rsync$(EXEEXT)
83+	ln -s rsync$(EXEEXT) rsyncdb$(EXEEXT)
84+
85 $(OBJS): $(HEADERS)
86 $(CHECK_OBJS): $(HEADERS)
87 tls.o xattrs.o: lib/sysxattrs.h
88-usage.o: latest-year.h help-rsync.h help-rsyncd.h git-version.h default-cvsignore.h
89+usage.o: latest-year.h help-rsync.h help-rsyncd.h help-rsyncdb.h git-version.h default-cvsignore.h
90 loadparm.o: default-dont-compress.h daemon-parm.h
91
92 flist.o: rounding.h
93@@ -110,6 +116,9 @@ default-cvsignore.h default-dont-compress.h: rsync.1.md define-from-md.awk
94 help-rsync.h help-rsyncd.h: rsync.1.md help-from-md.awk
95 	$(AWK) -f $(srcdir)/help-from-md.awk -v hfile=$@ $(srcdir)/rsync.1.md
96
97+help-rsyncdb.h: rsyncdb.1.md help-from-md.awk
98+	awk -f $(srcdir)/help-from-md.awk -v hfile=$@ $(srcdir)/rsyncdb.1.md
99+
100 daemon-parm.h: daemon-parm.txt daemon-parm.awk
101 	$(AWK) -f $(srcdir)/daemon-parm.awk $(srcdir)/daemon-parm.txt
102
103@@ -240,7 +249,7 @@ proto.h-tstamp: $(srcdir)/*.c $(srcdir)/lib/compat.c daemon-parm.h
104 	$(AWK) -f $(srcdir)/mkproto.awk $(srcdir)/*.c $(srcdir)/lib/compat.c daemon-parm.h
105
106 .PHONY: man
107-man: rsync.1 rsync-ssl.1 rsyncd.conf.5
108+man: rsync.1 rsync-ssl.1 rsyncd.conf.5 rsyncdb.1
109
110 rsync.1: rsync.1.md md2man version.h Makefile
111 	@$(srcdir)/maybe-make-man $(srcdir) rsync.1.md
112@@ -251,9 +260,12 @@ rsync-ssl.1: rsync-ssl.1.md md2man version.h Makefile
113 rsyncd.conf.5: rsyncd.conf.5.md md2man version.h Makefile
114 	@$(srcdir)/maybe-make-man $(srcdir) rsyncd.conf.5.md
115
116+rsyncdb.1: rsyncdb.1.md md2man NEWS.md Makefile
117+	@$(srcdir)/maybe-make-man $(srcdir) rsyncdb.1.md
118+
119 .PHONY: clean
120 clean: cleantests
121-	rm -f *~ $(OBJS) $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_SYMLINKS) \
122+	rm -f *~ $(OBJS) $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_SYMLINKS) rsyncdb$(EXEEXT) \
123 		rounding rounding.h *.old rsync*.1 rsync*.5 rsync*.html \
124 		daemon-parm.h help-*.h default-*.h proto.h proto.h-tstamp
125
126diff --git a/checksum.c b/checksum.c
127--- a/checksum.c
128+++ b/checksum.c
129@@ -40,6 +40,7 @@ extern int whole_file;
130 extern int checksum_seed;
131 extern int protocol_version;
132 extern int proper_seed_order;
133+extern int use_db;
134 extern const char *checksum_choice;
135
136 struct name_num_obj valid_checksums = {
137@@ -386,6 +387,8 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
138 			MD5_Update(&m5, (uchar *)map_ptr(buf, i, remainder), remainder);
139
140 		MD5_Final((uchar *)sum, &m5);
141+		if (use_db)
142+			db_set_checksum(5, st_p, sum);
143 		break;
144 	  }
145 	  case CSUM_MD4:
146@@ -425,6 +428,8 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
147 			mdfour_update(&m, (uchar *)map_ptr(buf, i, remainder), remainder);
148
149 		mdfour_result(&m, (uchar *)sum);
150+		if (use_db)
151+			db_set_checksum(4, st_p, sum);
152 		break;
153 	  }
154 	  default:
155diff --git a/cleanup.c b/cleanup.c
156--- a/cleanup.c
157+++ b/cleanup.c
158@@ -28,6 +28,7 @@ extern int am_daemon;
159 extern int am_receiver;
160 extern int am_sender;
161 extern int io_error;
162+extern int use_db;
163 extern int keep_partial;
164 extern int got_xfer_error;
165 extern int protocol_version;
166@@ -143,6 +144,12 @@ NORETURN void _exit_cleanup(int code, const char *file, int line)
167 #include "case_N.h"
168 		switch_step++;
169
170+		if (use_db)
171+			db_disconnect(False);
172+
173+		/* FALLTHROUGH */
174+#include "case_N.h"
175+
176 		if (cleanup_child_pid != -1) {
177 			int status;
178 			int pid = wait_process(cleanup_child_pid, &status, WNOHANG);
179diff --git a/clientserver.c b/clientserver.c
180--- a/clientserver.c
181+++ b/clientserver.c
182@@ -44,12 +44,15 @@ extern int numeric_ids;
183 extern int filesfrom_fd;
184 extern int remote_protocol;
185 extern int protocol_version;
186+extern int always_checksum;
187+extern int db_lax;
188 extern int io_timeout;
189 extern int no_detach;
190 extern int write_batch;
191 extern int default_af_hint;
192 extern int logfile_format_has_i;
193 extern int logfile_format_has_o_or_i;
194+extern char *db_config;
195 extern char *bind_address;
196 extern char *config_file;
197 extern char *logfile_format;
198@@ -809,6 +812,11 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
199
200 	log_init(1);
201
202+	if (*lp_db_config(i)) {
203+		db_read_config(FLOG, lp_db_config(i));
204+		db_lax = lp_db_lax(i);
205+	}
206+
207 #ifdef HAVE_PUTENV
208 	if ((*lp_early_exec(module_id) || *lp_prexfer_exec(module_id)
209 	  || *lp_postxfer_exec(module_id) || *lp_name_converter(module_id))
210@@ -1021,6 +1029,8 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
211
212 	am_server = 1; /* Don't let someone try to be tricky. */
213 	quiet = 0;
214+	db_config = NULL;
215+
216 	if (lp_ignore_errors(module_id))
217 		ignore_errors = 1;
218 	if (write_batch < 0)
219diff --git a/configure.ac b/configure.ac
220--- a/configure.ac
221+++ b/configure.ac
222@@ -494,6 +494,7 @@ AC_CHECK_HEADERS(sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \
223     unistd.h utime.h grp.h compat.h sys/param.h ctype.h sys/wait.h \
224     sys/ioctl.h sys/filio.h string.h stdlib.h sys/socket.h sys/mode.h \
225     sys/un.h sys/attr.h mcheck.h arpa/inet.h arpa/nameser.h locale.h \
226+    mysql/mysql.h sqlite3.h \
227     netdb.h malloc.h float.h limits.h iconv.h libcharset.h langinfo.h \
228     sys/acl.h acl/libacl.h attr/xattr.h sys/xattr.h sys/extattr.h dl.h \
229     popt.h popt/popt.h linux/falloc.h netinet/in_systm.h netinet/ip.h \
230@@ -1369,6 +1370,48 @@ if test x"$enable_acl_support" = x"no" -o x"$enable_xattr_support" = x"no" -o x"
231     fi
232 fi
233
234+AC_MSG_CHECKING([whether to include mysql DB support])
235+AC_ARG_ENABLE(mysql,
236+	AC_HELP_STRING([--disable-mysql],
237+		[disable mysql DB support]))
238+
239+if test x"$enable_mysql" = x"no"; then
240+    AC_MSG_RESULT(no)
241+else
242+    AC_MSG_RESULT([yes])
243+    AC_CHECK_PROG(MYSQL_CONFIG, mysql_config, 1, 0)
244+    if test x$MYSQL_CONFIG = x1; then
245+	AC_MSG_CHECKING(for mysql version >= 4)
246+	mysql_version=`mysql_config --version`
247+	mysql_major_version=`echo $mysql_version | sed 's/\..*//'`
248+	if test $mysql_major_version -lt 4; then
249+	    AC_MSG_RESULT(no.. skipping MySQL)
250+	else
251+	    AC_MSG_RESULT(yes)
252+
253+	    MYSQL_CFLAGS=`mysql_config --cflags`
254+	    MYSQL_LIBS=`mysql_config --libs`
255+
256+	    CPPFLAGS="$CPPFLAGS $MYSQL_CFLAGS"
257+	    LIBS="$MYSQL_LIBS $LIBS"
258+
259+	    AC_CHECK_LIB(mysqlclient, mysql_init)
260+	fi
261+    fi
262+fi
263+
264+AC_MSG_CHECKING([whether to include sqlite DB support])
265+AC_ARG_ENABLE(sqlite,
266+	AC_HELP_STRING([--disable-sqlite],
267+		[disable sqlite DB support]))
268+
269+if test x"$enable_sqlite" = x"no"; then
270+    AC_MSG_RESULT(no)
271+else
272+    AC_CHECK_LIB(sqlite3, sqlite3_open)
273+    AC_CHECK_FUNCS(sqlite3_open_v2 sqlite3_prepare_v2)
274+fi
275+
276 case "$CC" in
277 ' checker'*|checker*)
278     AC_DEFINE(FORCE_FD_ZERO_MEMSET, 1, [Used to make "checker" understand that FD_ZERO() clears memory.])
279diff --git a/daemon-parm.txt b/daemon-parm.txt
280--- a/daemon-parm.txt
281+++ b/daemon-parm.txt
282@@ -18,6 +18,7 @@ Locals: =================================================================
283 STRING	auth_users		NULL
284 STRING	charset			NULL
285 STRING	comment			NULL
286+STRING	db_config		NULL
287 STRING	dont_compress		DEFAULT_DONT_COMPRESS
288 STRING	early_exec		NULL
289 STRING	exclude			NULL
290@@ -51,6 +52,7 @@ INTEGER	timeout			0
291
292 ENUM	syslog_facility		LOG_DAEMON
293
294+BOOL	db_lax			False
295 BOOL	fake_super		False
296 BOOL	forward_lookup		True
297 BOOL	ignore_errors		False
298diff --git a/db.c b/db.c
299new file mode 100644
300--- /dev/null
301+++ b/db.c
302@@ -0,0 +1,1943 @@
303+/*
304+ * Routines to access extended file info via DB.
305+ *
306+ * Copyright (C) 2008-2013 Wayne Davison
307+ *
308+ * This program is free software; you can redistribute it and/or modify
309+ * it under the terms of the GNU General Public License as published by
310+ * the Free Software Foundation; either version 3 of the License, or
311+ * (at your option) any later version.
312+ *
313+ * This program is distributed in the hope that it will be useful,
314+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
315+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
316+ * GNU General Public License for more details.
317+ *
318+ * You should have received a copy of the GNU General Public License along
319+ * with this program; if not, visit the http://fsf.org website.
320+ */
321+
322+#include "rsync.h"
323+#include "ifuncs.h"
324+#include "itypes.h"
325+#include "inums.h"
326+#ifdef USE_OPENSSL
327+#include "openssl/md4.h"
328+#include "openssl/md5.h"
329+#endif
330+
331+extern int recurse;
332+extern int same_db;
333+extern int am_receiver;
334+extern int am_generator;
335+extern int checksum_type;
336+extern int db_clean, db_check, db_do_md4, db_do_md5, db_update, db_lax, db_init, db_mounts;
337+extern int db_output_name, db_output_sum, db_output_info, db_output_unchanged, db_output_dirs, db_output_msgs;
338+extern int saw_db_output_opt, saw_db_sum_opt;
339+extern char *db_config;
340+
341+#define MOUNT_HELPER_SCRIPT "/usr/sbin/rsyncdb-mountinfo"
342+
343+#if defined HAVE_MYSQL_MYSQL_H && defined HAVE_LIBMYSQLCLIENT
344+#define USE_MYSQL
345+#include <mysql/mysql.h>
346+#include <mysql/errmsg.h>
347+#endif
348+
349+#if defined HAVE_SQLITE3_H && defined HAVE_LIBSQLITE3
350+#define USE_SQLITE
351+#include <sqlite3.h>
352+#ifndef HAVE_SQLITE3_OPEN_V2
353+#define sqlite3_open_v2(dbname, dbhptr, flags, vfs) \
354+	sqlite3_open(dbname, dbhptr)
355+#endif
356+#ifndef HAVE_SQLITE3_PREPARE_V2
357+#define sqlite3_prepare_v2 sqlite3_prepare
358+#endif
359+#define MAX_LOCK_FAILURES 10
360+#define LOCK_FAIL_MSLEEP 100
361+#endif
362+
363+#ifndef USE_OPENSSL
364+#define MD5_CTX md_context
365+#define MD5_Init md5_begin
366+#define MD5_Update md5_update
367+#define MD5_Final(digest, cptr) md5_result(cptr, digest)
368+#endif
369+
370+#define DB_TYPE_NONE 0
371+#define DB_TYPE_MYSQL 1
372+#define DB_TYPE_SQLITE 2
373+
374+int use_db = DB_TYPE_NONE;
375+int select_many_sums = 0;
376+
377+#define PREP_NORM 0
378+#define PREP_MOUNT 1
379+
380+static const char *dbhost = NULL, *dbuser = NULL, *dbpass = NULL, *dbname = NULL;
381+static unsigned int dbport = 0;
382+static int transaction_state = -1;
383+
384+static union {
385+#ifdef USE_MYSQL
386+    MYSQL *mysql;
387+#endif
388+#ifdef USE_SQLITE
389+    sqlite3 *sqlite;
390+#endif
391+    void *all;
392+} dbh;
393+
394+#define SEL_DEV 0
395+#define SEL_SUM 1
396+#define REP_SUM 2
397+#define UPD_CTIME 3
398+#define INS_MOUNT 4
399+#define UPD_MOUNT 5 /* SQLite only */
400+#define SEL_MOUNT 6
401+#define UN_MOUNT 7
402+#define DEL_SUMS 8
403+#define INS_PRESENT 9
404+#define MAX_PREP_CNT 10
405+
406+#define MAX_BIND_CNT 7
407+#define MAX_RESULT_BINDS 32
408+
409+static union {
410+#ifdef USE_MYSQL
411+    MYSQL_STMT *mysql;
412+#endif
413+#ifdef USE_SQLITE
414+    sqlite3_stmt *sqlite;
415+#endif
416+    void *all;
417+} statements[MAX_PREP_CNT];
418+
419+static int md_num;
420+static enum logcode log_code;
421+
422+#ifdef USE_MYSQL
423+static unsigned int bind_disk_id, bind_mdnum;
424+static int64 bind_devno, bind_ino, bind_size, bind_mtime, bind_ctime;
425+static char bind_sum[MAX_DIGEST_LEN];
426+static unsigned long result_length[MAX_RESULT_BINDS];
427+static bool result_is_null[MAX_RESULT_BINDS], result_error[MAX_RESULT_BINDS];
428+#elif defined USE_SQLITE
429+static int64 bind_mtime;
430+#endif
431+static char bind_thishost[128+1];
432+static unsigned long bind_thishost_len;
433+static char *mount_helper_script = NULL;
434+
435+static char *error_log;
436+#if defined USE_SQLITE && defined SQLITE_CONFIG_LOG
437+static char bind_mount_uniq[128+1];
438+static unsigned long bind_mount_uniq_len;
439+static FILE *error_log_fp;
440+#endif
441+
442+#define PTR_SIZE (sizeof (struct file_struct *))
443+
444+#if defined USE_MYSQL || defined USE_SQLITE
445+static void update_mounts(void);
446+#endif
447+
448+struct name_list {
449+	struct name_list *next;
450+	char name[1];
451+} *dirs_list;
452+
453+int db_read_config(enum logcode code, const char *config_file)
454+{
455+	char buf[2048], *cp;
456+	FILE *fp;
457+	int lineno = 0;
458+
459+	log_code = code;
460+
461+	bind_thishost_len = strlcpy(bind_thishost, "localhost", sizeof bind_thishost);
462+
463+	if (!(fp = fopen(config_file, "r"))) {
464+		rsyserr(log_code, errno, "unable to open %s", config_file);
465+		return 0;
466+	}
467+	if (DEBUG_GTE(DB, 1))
468+		rprintf(FCLIENT, "[%s] Reading DB config from %s\n", who_am_i(), config_file);
469+	while (fgets(buf, sizeof buf, fp)) {
470+		lineno++;
471+		if ((cp = strchr(buf, '#')) == NULL
472+		 && (cp = strchr(buf, '\r')) == NULL
473+		 && (cp = strchr(buf, '\n')) == NULL)
474+			cp = buf + strlen(buf);
475+		while (cp != buf && isSpace(cp-1)) cp--;
476+		*cp = '\0';
477+
478+		if (!*buf)
479+			continue;
480+
481+		if (!(cp = strchr(buf, ':')))
482+			goto invalid_line;
483+		*cp++ = '\0';
484+
485+		while (isSpace(cp)) cp++;
486+		if (strcasecmp(buf, "dbhost") == 0)
487+			dbhost = strdup(cp);
488+		else if (strcasecmp(buf, "dbuser") == 0)
489+			dbuser = strdup(cp);
490+		else if (strcasecmp(buf, "dbpass") == 0)
491+			dbpass = strdup(cp);
492+		else if (strcasecmp(buf, "dbname") == 0)
493+			dbname = strdup(cp);
494+		else if (strcasecmp(buf, "dbport") == 0)
495+			dbport = atoi(cp);
496+		else if (strcasecmp(buf, "transaction") == 0)
497+			transaction_state = atoi(cp) ? 0 : -1;
498+		else if (strcasecmp(buf, "mountHelper") == 0)
499+			mount_helper_script = strdup(cp);
500+		else if (strcasecmp(buf, "errlog") == 0)
501+			error_log = strdup(cp);
502+		else if (strcasecmp(buf, "thishost") == 0)
503+			bind_thishost_len = strlcpy(bind_thishost, cp, sizeof bind_thishost);
504+		else if (strcasecmp(buf, "dbtype") == 0) {
505+#ifdef USE_MYSQL
506+			if (strcasecmp(cp, "mysql") == 0) {
507+				use_db = DB_TYPE_MYSQL;
508+				continue;
509+			}
510+#endif
511+#ifdef USE_SQLITE
512+			if (strcasecmp(cp, "sqlite") == 0) {
513+				use_db = DB_TYPE_SQLITE;
514+				continue;
515+			}
516+#endif
517+			rprintf(log_code,
518+			    "Unsupported dbtype on line #%d in %s.\n",
519+			    lineno, config_file);
520+			use_db = DB_TYPE_NONE;
521+			return 0;
522+		} else {
523+		  invalid_line:
524+			rprintf(log_code, "Invalid line #%d in %s\n",
525+				lineno, config_file);
526+			use_db = DB_TYPE_NONE;
527+			return 0;
528+		}
529+	}
530+	fclose(fp);
531+
532+	if (bind_thishost_len >= (int)sizeof bind_thishost)
533+		bind_thishost_len = sizeof bind_thishost - 1;
534+
535+	if (!use_db || !dbname) {
536+		rprintf(log_code, "Please specify at least dbtype and dbname in %s.\n", config_file);
537+		use_db = DB_TYPE_NONE;
538+		return 0;
539+	}
540+
541+	md_num = checksum_type == 5 ? 5 : 4;
542+
543+	if (error_log) {
544+		if (use_db != DB_TYPE_SQLITE)
545+			rprintf(log_code, "Ignoring errlog setting for non-SQLite DB.\n");
546+#ifndef SQLITE_CONFIG_LOG
547+		else
548+			rprintf(log_code, "Your sqlite doesn't support SQLITE_CONFIG_LOG.\n");
549+#endif
550+	}
551+
552+	if (!mount_helper_script)
553+		mount_helper_script = MOUNT_HELPER_SCRIPT;
554+
555+	return 1;
556+}
557+
558+#if defined USE_SQLITE && defined SQLITE_CONFIG_LOG
559+static void errorLogCallback(UNUSED(void *pArg), int iErrCode, const char *zMsg)
560+{
561+	fprintf(error_log_fp, "[%d] %s (%d)\n", (int)getpid(), zMsg, iErrCode);
562+}
563+#endif
564+
565+static int run_sql(const char *fmt, ...)
566+{
567+	va_list ap;
568+	char *query;
569+	int ok = 0, qlen;
570+
571+	va_start(ap, fmt);
572+	qlen = vasprintf(&query, fmt, ap);
573+	va_end(ap);
574+	if (qlen < 0)
575+		out_of_memory("run_sql");
576+	if (DEBUG_GTE(DB, 3))
577+		rprintf(FCLIENT, "[%s] SQL being run: %s\n", who_am_i(), query);
578+
579+	switch (use_db) {
580+#ifdef USE_MYSQL
581+	case DB_TYPE_MYSQL:
582+		if (mysql_query(dbh.mysql, query) < 0) {
583+			rprintf(FERROR, "Failed to run sql: %s\n", mysql_error(dbh.mysql));
584+			rprintf(FERROR, "%s\n", query);
585+		} else
586+			ok = 1;
587+		break;
588+#endif
589+#ifdef USE_SQLITE
590+	case DB_TYPE_SQLITE: {
591+		int rc, lock_failures = 0;
592+		while (1) {
593+			if ((rc = sqlite3_exec(dbh.sqlite, query, NULL, NULL, NULL)) == 0)
594+				break;
595+			if (rc != SQLITE_BUSY && rc != SQLITE_LOCKED)
596+				break;
597+			if (++lock_failures > MAX_LOCK_FAILURES)
598+				break;
599+			msleep(LOCK_FAIL_MSLEEP);
600+		}
601+		if (rc) {
602+			rprintf(FERROR, "[%s] Failed to run sql: %s\n", who_am_i(), sqlite3_errmsg(dbh.sqlite));
603+			rprintf(FERROR, "%s\n", query);
604+		} else
605+			ok = 1;
606+		break;
607+	    }
608+#endif
609+	}
610+
611+	free(query);
612+
613+	return ok;
614+}
615+
616+#ifdef USE_MYSQL
617+static int prepare_mysql(int ndx, MYSQL_BIND *binds, int bind_cnt, const char *fmt, ...)
618+{
619+	va_list ap;
620+	char *query;
621+	int qlen, param_cnt;
622+	MYSQL_STMT *stmt = mysql_stmt_init(dbh.mysql);
623+
624+	if (stmt == NULL)
625+		out_of_memory("prepare_mysql");
626+
627+	va_start(ap, fmt);
628+	qlen = vasprintf(&query, fmt, ap);
629+	va_end(ap);
630+	if (qlen < 0)
631+		out_of_memory("prepare_mysql");
632+	if (DEBUG_GTE(DB, 3))
633+		rprintf(FCLIENT, "[%s] SQL being prepared: %s\n", who_am_i(), query);
634+
635+	if (mysql_stmt_prepare(stmt, query, qlen) != 0) {
636+		rprintf(log_code, "[%s] Prepare failed: %s\n", who_am_i(), mysql_stmt_error(stmt));
637+		rprintf(log_code, "%s\n", query);
638+		free(query);
639+		return 0;
640+	}
641+
642+	if ((param_cnt = mysql_stmt_param_count(stmt)) != bind_cnt) {
643+		rprintf(log_code, "[%s] Parameters in statement = %d, bind vars = %d\n",
644+			who_am_i(), param_cnt, bind_cnt);
645+		rprintf(log_code, "%s\n", query);
646+		free(query);
647+		return 0;
648+	}
649+	if (bind_cnt)
650+		mysql_stmt_bind_param(stmt, binds);
651+
652+	statements[ndx].mysql = stmt;
653+	free(query);
654+
655+	return 1;
656+}
657+#endif
658+
659+#ifdef USE_MYSQL
660+static int prepare_mysql_queries(int type)
661+{
662+	MYSQL_BIND binds[MAX_BIND_CNT];
663+	char *sql;
664+
665+	switch (type) {
666+	case PREP_NORM:
667+		sql="SELECT disk_id"
668+		    " FROM disk"
669+		    " WHERE host = ? AND devno = ?";
670+		memset(binds, 0, sizeof binds);
671+		binds[0].buffer_type = MYSQL_TYPE_STRING;
672+		binds[0].buffer = &bind_thishost;
673+		binds[0].buffer_length = bind_thishost_len;
674+		binds[1].buffer_type = MYSQL_TYPE_LONGLONG;
675+		binds[1].buffer = &bind_devno;
676+		if (!prepare_mysql(SEL_DEV, binds, 2, sql))
677+			return 0;
678+
679+		memset(binds, 0, sizeof binds);
680+		binds[0].buffer_type = MYSQL_TYPE_LONG;
681+		binds[0].buffer = &bind_disk_id;
682+		binds[1].buffer_type = MYSQL_TYPE_LONGLONG;
683+		binds[1].buffer = &bind_ino;
684+		if (select_many_sums) {
685+			sql="SELECT checksum, sum_type, size, mtime, ctime"
686+			    " FROM inode_map"
687+			    " WHERE disk_id = ? AND ino = ?";
688+			if (!prepare_mysql(SEL_SUM, binds, 2, sql))
689+				return 0;
690+		} else {
691+			sql="SELECT checksum"
692+			    " FROM inode_map"
693+			    " WHERE disk_id = ? AND ino = ? AND sum_type = %d"
694+			    "   AND size = ? AND mtime = ? %s"; /* optional: AND ctime = ? */
695+			binds[2].buffer_type = MYSQL_TYPE_LONGLONG;
696+			binds[2].buffer = &bind_size;
697+			binds[3].buffer_type = MYSQL_TYPE_LONGLONG;
698+			binds[3].buffer = &bind_mtime;
699+			if (!db_lax) {
700+				binds[4].buffer_type = MYSQL_TYPE_LONGLONG;
701+				binds[4].buffer = &bind_ctime;
702+			}
703+			if (!prepare_mysql(SEL_SUM, binds, 4 + !db_lax, sql, md_num, db_lax ? "" : "AND ctime = ?"))
704+				return 0;
705+		}
706+
707+		sql="INSERT INTO inode_map"
708+		    " SET disk_id = ?, ino = ?, sum_type = ?,"
709+		    "     size = ?, mtime = ?, ctime = ?, checksum = ?"
710+		    " ON DUPLICATE KEY"
711+		    " UPDATE size = VALUES(size), mtime = VALUES(mtime),"
712+		    "        ctime = VALUES(ctime), checksum = VALUES(checksum)";
713+		memset(binds, 0, sizeof binds);
714+		binds[0].buffer_type = MYSQL_TYPE_LONG;
715+		binds[0].buffer = &bind_disk_id;
716+		binds[1].buffer_type = MYSQL_TYPE_LONGLONG;
717+		binds[1].buffer = &bind_ino;
718+		binds[2].buffer_type = MYSQL_TYPE_LONG;
719+		binds[2].buffer = &bind_mdnum;
720+		binds[3].buffer_type = MYSQL_TYPE_LONGLONG;
721+		binds[3].buffer = &bind_size;
722+		binds[4].buffer_type = MYSQL_TYPE_LONGLONG;
723+		binds[4].buffer = &bind_mtime;
724+		binds[5].buffer_type = MYSQL_TYPE_LONGLONG;
725+		binds[5].buffer = &bind_ctime;
726+		binds[6].buffer_type = MYSQL_TYPE_BLOB;
727+		binds[6].buffer = &bind_sum;
728+		binds[6].buffer_length = MD5_DIGEST_LEN; /* Same as MD4_DIGEST_LEN */
729+		if (!prepare_mysql(REP_SUM, binds, 7, sql))
730+			return 0;
731+
732+		sql="UPDATE inode_map"
733+		    " SET ctime = ?"
734+		    " WHERE disk_id = ? AND ino = ? AND sum_type = ? AND size = ? AND mtime = ?";
735+		memset(binds, 0, sizeof binds);
736+		binds[0].buffer_type = MYSQL_TYPE_LONGLONG;
737+		binds[0].buffer = &bind_ctime;
738+		binds[1].buffer_type = MYSQL_TYPE_LONG;
739+		binds[1].buffer = &bind_disk_id;
740+		binds[2].buffer_type = MYSQL_TYPE_LONGLONG;
741+		binds[2].buffer = &bind_ino;
742+		binds[3].buffer_type = MYSQL_TYPE_LONG;
743+		binds[3].buffer = &bind_mdnum;
744+		binds[4].buffer_type = MYSQL_TYPE_LONGLONG;
745+		binds[4].buffer = &bind_size;
746+		binds[5].buffer_type = MYSQL_TYPE_LONGLONG;
747+		binds[5].buffer = &bind_mtime;
748+		if (!prepare_mysql(UPD_CTIME, binds, 6, sql))
749+			return 0;
750+		break;
751+
752+	case PREP_MOUNT:
753+		sql="INSERT INTO disk"
754+		    " SET host = ?, last_seen = ?, mount_uniq = ?, devno = ?"
755+		    " ON DUPLICATE KEY"
756+		    " UPDATE last_seen = VALUES(last_seen), devno = VALUES(devno)";
757+		memset(binds, 0, sizeof binds);
758+		binds[0].buffer_type = MYSQL_TYPE_STRING;
759+		binds[0].buffer = &bind_thishost;
760+		binds[0].buffer_length = bind_thishost_len;
761+		binds[1].buffer_type = MYSQL_TYPE_LONGLONG;
762+		binds[1].buffer = &bind_mtime; /* we abuse mtime to hold the last_seen value */
763+		binds[2].buffer_type = MYSQL_TYPE_STRING;
764+		binds[2].buffer = &bind_mount_uniq;
765+		binds[2].buffer_length = sizeof bind_mount_uniq;
766+		binds[2].length = &bind_mount_uniq_len;
767+		binds[3].buffer_type = MYSQL_TYPE_LONGLONG;
768+		binds[3].buffer = &bind_devno;
769+		if (!prepare_mysql(INS_MOUNT, binds, 4, sql))
770+			return 0;
771+
772+		sql="SELECT mount_uniq"
773+		    " FROM disk"
774+		    " WHERE host = ? AND last_seen < ? AND devno != 0";
775+		/* Reusing first 2 binds from INS_MOUNT */
776+		if (!prepare_mysql(SEL_MOUNT, binds, 2, sql))
777+			return 0;
778+
779+		sql="UPDATE disk"
780+		    " SET devno = 0"
781+		    " WHERE host = ? AND last_seen < ? AND devno != 0";
782+		/* Reusing binds from SEL_MOUNT */
783+		if (!prepare_mysql(UN_MOUNT, binds, 2, sql))
784+			return 0;
785+		break;
786+	}
787+
788+	return 1;
789+}
790+#endif
791+
792+#ifdef USE_MYSQL
793+static int db_connect_mysql(void)
794+{
795+	const char *open_dbname = db_init ? "mysql" : dbname;
796+
797+	if (!(dbh.mysql = mysql_init(NULL)))
798+		out_of_memory("db_read_config");
799+
800+	if (DEBUG_GTE(DB, 1)) {
801+		rprintf(FCLIENT, "[%s] connecting: host=%s user=%s db=%s port=%d\n",
802+			who_am_i(), dbhost, dbuser, open_dbname, dbport);
803+	}
804+	if (!mysql_real_connect(dbh.mysql, dbhost, dbuser, dbpass, open_dbname, dbport, NULL, 0)) {
805+		rprintf(log_code, "[%s] Unable to connect to DB: %s\n", who_am_i(), mysql_error(dbh.mysql));
806+		return 0;
807+	}
808+
809+	if (db_init) {
810+		if (db_output_msgs)
811+			rprintf(FCLIENT, "Creating DB %s (if it does not exist)\n", dbname);
812+		if (!run_sql("CREATE DATABASE IF NOT EXISTS `%s`", dbname)
813+		 || !run_sql("USE `%s`", dbname))
814+			exit_cleanup(RERR_IPC);
815+
816+		if (db_output_msgs)
817+			rprintf(FCLIENT, "Dropping old tables (if they exist))\n");
818+		if (!run_sql("DROP TABLE IF EXISTS disk")
819+		 || !run_sql("DROP TABLE IF EXISTS inode_map"))
820+			exit_cleanup(RERR_IPC);
821+
822+		if (db_output_msgs)
823+			rprintf(FCLIENT, "Creating empty tables ...\n");
824+		if (!run_sql(
825+		    "CREATE TABLE disk (\n"
826+		    "  disk_id integer unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,\n"
827+		    "  host varchar(128) NOT NULL default 'localhost',\n"
828+		    "  mount_uniq varchar(128) default NULL,\n"
829+		    "  devno bigint unsigned NOT NULL,\n" /* This is 0 when not mounted */
830+		    "  last_seen bigint NOT NULL,\n"
831+		    "  UNIQUE KEY mount_lookup (host, mount_uniq),\n"
832+		    "  KEY dev_lookup (devno, host)\n"
833+		    ")"))
834+			exit_cleanup(RERR_IPC);
835+
836+		if (!run_sql(
837+		    "CREATE TABLE inode_map (\n"
838+		    "  disk_id integer unsigned NOT NULL,\n"
839+		    "  ino bigint unsigned NOT NULL,\n"
840+		    "  sum_type tinyint NOT NULL default '0',\n"
841+		    "  size bigint unsigned NOT NULL,\n"
842+		    "  mtime bigint NOT NULL,\n"
843+		    "  ctime bigint NOT NULL,\n"
844+		    "  checksum binary(16) NOT NULL,\n"
845+		    "  PRIMARY KEY (disk_id,ino,sum_type)\n"
846+		    ")"))
847+			exit_cleanup(RERR_IPC);
848+
849+		if (!db_mounts)
850+			exit_cleanup(0);
851+	}
852+
853+	if (db_mounts) {
854+		if (!prepare_mysql_queries(PREP_MOUNT))
855+			exit_cleanup(RERR_IPC);
856+		update_mounts();
857+		exit_cleanup(0);
858+	}
859+
860+	if (!prepare_mysql_queries(PREP_NORM))
861+		return 0;
862+
863+	return 1;
864+}
865+#endif
866+
867+#ifdef USE_SQLITE
868+static int prepare_sqlite(int ndx, const char *fmt, ...)
869+{
870+	va_list ap;
871+	char *query;
872+	int rc, qlen, lock_failures = 0;
873+
874+	va_start(ap, fmt);
875+	qlen = vasprintf(&query, fmt, ap);
876+	va_end(ap);
877+	if (qlen < 0)
878+		out_of_memory("prepare_sqlite");
879+	if (DEBUG_GTE(DB, 3))
880+		rprintf(FCLIENT, "[%s] SQL being prepared: %s\n", who_am_i(), query);
881+
882+	while ((rc = sqlite3_prepare_v2(dbh.sqlite, query, -1, &statements[ndx].sqlite, NULL)) != 0) {
883+		if (DEBUG_GTE(DB, 4)) {
884+			rprintf(FCLIENT, "[%s] sqlite3_prepare_v2(,%s,,) returned %d\n",
885+				who_am_i(), query, rc);
886+		}
887+		if (rc != SQLITE_BUSY && rc != SQLITE_LOCKED)
888+			break;
889+		if (++lock_failures > MAX_LOCK_FAILURES)
890+			break;
891+		msleep(LOCK_FAIL_MSLEEP);
892+	}
893+	if (rc) {
894+		rprintf(log_code, "[%s] Failed to prepare SQL: %s (%d)\n", who_am_i(), sqlite3_errmsg(dbh.sqlite), rc);
895+		rprintf(log_code, "%s\n", query);
896+		free(query);
897+		return 0;
898+	}
899+	free(query);
900+
901+	return 1;
902+}
903+#endif
904+
905+#ifdef USE_SQLITE
906+static int prepare_sqlite_queries(int type)
907+{
908+	char *sql;
909+
910+	switch (type) {
911+	case PREP_NORM:
912+		sql="SELECT disk_id"
913+		    " FROM disk"
914+		    " WHERE host = ? AND devno = ?";
915+		if (!prepare_sqlite(SEL_DEV, sql))
916+			return 0;
917+
918+		if (select_many_sums) {
919+			sql="SELECT checksum, sum_type, size, mtime, ctime"
920+			    " FROM inode_map"
921+			    " WHERE disk_id = ? AND ino = ?";
922+			if (!prepare_sqlite(SEL_SUM, sql))
923+				return 0;
924+		} else {
925+			sql="SELECT checksum"
926+			    " FROM inode_map"
927+			    " WHERE disk_id = ? AND ino = ? AND sum_type = %d"
928+			    "   AND size = ? AND mtime = ? %s";
929+			if (!prepare_sqlite(SEL_SUM, sql, md_num, db_lax ? "" : "AND ctime = ?"))
930+				return 0;
931+		}
932+
933+		sql="INSERT OR REPLACE INTO inode_map"
934+		    " (disk_id, ino, sum_type, size, mtime, ctime, checksum)"
935+		    " VALUES (?, ?, ?, ?, ?, ?, ?)";
936+		if (!prepare_sqlite(REP_SUM, sql))
937+			return 0;
938+
939+		sql="UPDATE inode_map"
940+		    " SET ctime = ?"
941+		    " WHERE disk_id = ? AND ino = ? AND sum_type = ? AND size = ? AND mtime = ?";
942+		if (!prepare_sqlite(UPD_CTIME, sql))
943+			return 0;
944+		break;
945+
946+	case PREP_MOUNT:
947+		sql="INSERT OR IGNORE INTO disk"
948+		    " (host, last_seen, mount_uniq, devno)"
949+		    " VALUES (?, ?, ?, ?)";
950+		if (!prepare_sqlite(INS_MOUNT, sql))
951+			return 0;
952+
953+		sql="UPDATE disk"
954+		    " SET last_seen = ?, devno = ?"
955+		    " WHERE host = ? AND mount_uniq = ?";
956+		if (!prepare_sqlite(UPD_MOUNT, sql))
957+			return 0;
958+
959+		sql="SELECT mount_uniq"
960+		    " FROM disk"
961+		    " WHERE host = ? AND last_seen < ? AND devno != 0";
962+		if (!prepare_sqlite(SEL_MOUNT, sql))
963+			return 0;
964+
965+		sql="UPDATE disk"
966+		    " SET devno = 0"
967+		    " WHERE host = ? AND last_seen < ? AND devno != 0";
968+		if (!prepare_sqlite(UN_MOUNT, sql))
969+			return 0;
970+		break;
971+	}
972+
973+	return 1;
974+}
975+#endif
976+
977+#ifdef USE_SQLITE
978+static int db_connect_sqlite(void)
979+{
980+	int lock_failures = 0;
981+	int rc;
982+
983+#ifdef SQLITE_CONFIG_LOG
984+	if (error_log) {
985+		if (DEBUG_GTE(DB, 1))
986+			rprintf(FCLIENT, "[%s] Setting sqlite errlog to %s\n", who_am_i(), error_log);
987+		if (!(error_log_fp = fopen(error_log, "a"))) {
988+			rsyserr(log_code, errno, "unable to append to logfile %s", error_log);
989+			error_log = NULL;
990+		} else if (sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, NULL) != 0)
991+			rprintf(log_code, "Failed to set errorLogCallback: %s\n", sqlite3_errmsg(dbh.sqlite));
992+	}
993+#endif
994+
995+	while (1) {
996+		int open_flags = SQLITE_OPEN_READWRITE;
997+		if (db_init)
998+			open_flags |= SQLITE_OPEN_CREATE;
999+		if (DEBUG_GTE(DB, 1))
1000+			rprintf(FCLIENT, "[%s] opening %s (%d)\n", who_am_i(), dbname, open_flags);
1001+		if ((rc = sqlite3_open_v2(dbname, &dbh.sqlite, open_flags, NULL)) == 0) {
1002+			break;
1003+		}
1004+		if (DEBUG_GTE(DB, 4)) {
1005+			rprintf(FCLIENT, "[%s] sqlite3_open_v2(%s,,%d,NULL) returned %d\n",
1006+				who_am_i(), dbname, open_flags, rc);
1007+		}
1008+		if (rc != SQLITE_BUSY && rc != SQLITE_LOCKED)
1009+			break;
1010+		if (++lock_failures > MAX_LOCK_FAILURES)
1011+			break;
1012+		msleep(LOCK_FAIL_MSLEEP);
1013+	}
1014+
1015+	if (rc) {
1016+		rprintf(log_code, "Unable to connect to DB: %s (%d)\n", sqlite3_errmsg(dbh.sqlite), rc);
1017+		return 0;
1018+	}
1019+
1020+	if (db_init) {
1021+		char *sql;
1022+		if (db_output_msgs)
1023+			rprintf(FCLIENT, "Dropping old tables (if they exist) ...\n");
1024+		if (!run_sql("DROP TABLE IF EXISTS disk")
1025+		 || !run_sql("DROP TABLE IF EXISTS inode_map"))
1026+			exit_cleanup(RERR_IPC);
1027+
1028+		if (db_output_msgs)
1029+			rprintf(FCLIENT, "Creating empty tables ...\n");
1030+		sql="CREATE TABLE disk (\n"
1031+		    "  disk_id integer NOT NULL PRIMARY KEY AUTOINCREMENT,\n"
1032+		    "  host varchar(128) NOT NULL default 'localhost',\n"
1033+		    "  mount_uniq varchar(128) default NULL,\n"
1034+		    "  devno bigint NOT NULL,\n" /* This is 0 when not mounted */
1035+		    "  last_seen bigint NOT NULL,\n"
1036+		    "  UNIQUE (host, mount_uniq)\n"
1037+		    ")";
1038+		if (!run_sql(sql))
1039+			exit_cleanup(RERR_IPC);
1040+
1041+		sql="CREATE TABLE inode_map (\n"
1042+		    "  disk_id integer NOT NULL,\n"
1043+		    "  ino bigint NOT NULL,\n"
1044+		    "  size bigint NOT NULL,\n"
1045+		    "  mtime bigint NOT NULL,\n"
1046+		    "  ctime bigint NOT NULL,\n"
1047+		    "  sum_type tinyint NOT NULL default '0',\n"
1048+		    "  checksum binary(16) NOT NULL,\n"
1049+		    "  PRIMARY KEY (disk_id,ino,sum_type)\n"
1050+		    ")";
1051+		if (!run_sql(sql))
1052+			exit_cleanup(RERR_IPC);
1053+
1054+#if SQLITE_VERSION_NUMBER >= 3007000
1055+		/* Using WAL locking makes concurrency much better (requires sqlite 3.7.0). */
1056+		sql="PRAGMA journal_mode = wal";
1057+		run_sql(sql); /* We don't check this for success. */
1058+#endif
1059+
1060+		if (!db_mounts)
1061+			exit_cleanup(0);
1062+	}
1063+
1064+	if (db_mounts) {
1065+		if (!prepare_sqlite_queries(PREP_MOUNT))
1066+			exit_cleanup(RERR_IPC);
1067+		update_mounts();
1068+		exit_cleanup(0);
1069+	}
1070+
1071+	if (!prepare_sqlite_queries(PREP_NORM)) {
1072+		db_disconnect(False);
1073+		return 0;
1074+	}
1075+
1076+	return 1;
1077+}
1078+#endif
1079+
1080+int db_connect(int select_many)
1081+{
1082+	select_many_sums = select_many;
1083+
1084+	switch (use_db) {
1085+#ifdef USE_MYSQL
1086+	case DB_TYPE_MYSQL:
1087+		if (db_connect_mysql())
1088+			return 1;
1089+		break;
1090+#endif
1091+#ifdef USE_SQLITE
1092+	case DB_TYPE_SQLITE:
1093+		if (db_connect_sqlite())
1094+			return 1;
1095+		break;
1096+#endif
1097+	}
1098+
1099+	db_disconnect(False);
1100+
1101+	return 0;
1102+}
1103+
1104+void db_disconnect(BOOL commit)
1105+{
1106+	int ndx;
1107+
1108+	if (!dbh.all)
1109+		return;
1110+
1111+	if (transaction_state > 0) {
1112+		if (DEBUG_GTE(DB, 1)) {
1113+			rprintf(FCLIENT, "[%s] %s our DB transaction\n",
1114+				who_am_i(), commit ? "Committing" : "Rolling back");
1115+		}
1116+		transaction_state = 0;
1117+		if (commit)
1118+			run_sql("COMMIT");
1119+		else
1120+			run_sql("ROLLBACK");
1121+	}
1122+
1123+	if (DEBUG_GTE(DB, 1))
1124+		rprintf(FCLIENT, "[%s] Disconnecting from the DB\n", who_am_i());
1125+
1126+	for (ndx = 0; ndx < MAX_PREP_CNT; ndx++) {
1127+		if (statements[ndx].all) {
1128+			switch (use_db) {
1129+#ifdef USE_MYSQL
1130+			case DB_TYPE_MYSQL:
1131+				mysql_stmt_close(statements[ndx].mysql);
1132+				break;
1133+#endif
1134+#ifdef USE_SQLITE
1135+			case DB_TYPE_SQLITE:
1136+				sqlite3_finalize(statements[ndx].sqlite);
1137+				break;
1138+#endif
1139+			}
1140+			statements[ndx].all = NULL;
1141+		}
1142+	}
1143+
1144+	switch (use_db) {
1145+#ifdef USE_MYSQL
1146+	case DB_TYPE_MYSQL:
1147+		mysql_close(dbh.mysql);
1148+		break;
1149+#endif
1150+#ifdef USE_SQLITE
1151+	case DB_TYPE_SQLITE:
1152+		sqlite3_close(dbh.sqlite);
1153+		break;
1154+#endif
1155+	}
1156+
1157+	dbh.all = NULL;
1158+	use_db = DB_TYPE_NONE;
1159+}
1160+
1161+#ifdef USE_MYSQL
1162+static MYSQL_STMT *exec_mysql(int ndx)
1163+{
1164+	MYSQL_STMT *stmt = statements[ndx].mysql;
1165+	int rc;
1166+
1167+	if ((rc = mysql_stmt_execute(stmt)) == CR_SERVER_LOST) {
1168+		db_disconnect(False);
1169+		use_db = DB_TYPE_MYSQL;
1170+		if (db_connect(select_many_sums)) {
1171+			stmt = statements[ndx].mysql;
1172+			rc = mysql_stmt_execute(stmt);
1173+		}
1174+	}
1175+	if (rc != 0) {
1176+		rprintf(log_code, "SQL execute failed: %s\n", mysql_stmt_error(stmt));
1177+		return NULL;
1178+	}
1179+
1180+	return stmt;
1181+}
1182+#endif
1183+
1184+#ifdef USE_MYSQL
1185+/* This stores up to max_rows into the values pointed to by the bind data arrays.
1186+ * If max_rows is > 1, then all the buffer pointers MUST be set to an array long
1187+ * enough to hold the max count of rows.  The buffer pointer will be incremented
1188+ * to read additional rows (but never past the end).  If stmt_ptr is non-NULL, it
1189+ * will be set to the "stmt" pointer IFF we didn't run out of rows before hitting
1190+ * the max.  In this case, the caller should call mysql_stmt_fetch() to read any
1191+ * remaining rows (the buffer pointers will point at the final array element) and
1192+ * then call mysql_stmt_free_result().  If *stmt_ptr is a NULL value, there were
1193+ * not enough rows to fill the max_rows arrays, and the stmt was already freed. */
1194+static int fetch_mysql(MYSQL_BIND *binds, int bind_cnt, int ndx, int max_rows, MYSQL_STMT **stmt_ptr)
1195+{
1196+	MYSQL_STMT *stmt;
1197+	int i, rc, rows = 0;
1198+
1199+	if (bind_cnt > MAX_RESULT_BINDS) {
1200+		fprintf(stderr, "Internal error: MAX_RESULT_BINDS overflow\n");
1201+		exit_cleanup(RERR_UNSUPPORTED);
1202+	}
1203+
1204+	if ((stmt = exec_mysql(ndx)) == NULL)
1205+		return 0;
1206+
1207+	for (i = 0; i < bind_cnt; i++) {
1208+		binds[i].is_null = &result_is_null[i];
1209+		binds[i].length = &result_length[i];
1210+		binds[i].error = &result_error[i];
1211+	}
1212+	mysql_stmt_bind_result(stmt, binds);
1213+
1214+	while (rows < max_rows) {
1215+		if ((rc = mysql_stmt_fetch(stmt)) != 0) {
1216+			if (rc != MYSQL_NO_DATA)
1217+				rprintf(log_code, "SELECT fetch failed: %s\n", mysql_stmt_error(stmt));
1218+			break;
1219+		}
1220+		if (++rows >= max_rows)
1221+			break;
1222+		for (i = 0; i < bind_cnt; i++) {
1223+			switch (binds[i].buffer_type) {
1224+			case MYSQL_TYPE_BLOB:
1225+			case MYSQL_TYPE_STRING:
1226+			    binds[i].buffer += binds[i].buffer_length;
1227+			    break;
1228+			case MYSQL_TYPE_LONG:
1229+			    binds[i].buffer += sizeof (int);
1230+			    break;
1231+			case MYSQL_TYPE_LONGLONG:
1232+			    binds[i].buffer += sizeof (int64);
1233+			    break;
1234+			default:
1235+			    fprintf(stderr, "Unknown MYSQL_TYPE_* in multi-row read: %d.\n", binds[i].buffer_type);
1236+			    exit_cleanup(RERR_UNSUPPORTED);
1237+			}
1238+		}
1239+	}
1240+
1241+	if (!stmt_ptr || rows < max_rows) {
1242+		mysql_stmt_free_result(stmt);
1243+		stmt = NULL;
1244+	}
1245+	if (stmt_ptr)
1246+		*stmt_ptr = stmt;
1247+
1248+	return rows;
1249+}
1250+#endif
1251+
1252+#if defined USE_MYSQL || defined USE_SQLITE
1253+static void update_mounts(void)
1254+{
1255+	char buf[2048], *argv[2];
1256+	int f_from, f_to, len;
1257+	STRUCT_STAT st;
1258+	int pid, status;
1259+
1260+	if (DEBUG_GTE(DB, 2))
1261+		printf("Running %s to grab mount info\n", mount_helper_script);
1262+	argv[0] = mount_helper_script;
1263+	argv[1] = NULL;
1264+	pid = piped_child(argv, &f_from, &f_to);
1265+	close(f_to);
1266+
1267+	bind_mtime = time(NULL); /* abuse mtime slightly to hold our last_seen value */
1268+
1269+	/* Strict format has 2 items with one tab as separator: MOUNT_UNIQ\tPATH */
1270+	while ((len = read_line(f_from, buf, sizeof buf, 0)) > 0) {
1271+		char *mount_uniq, *path;
1272+
1273+		if (DEBUG_GTE(DB, 3))
1274+			printf("Parsing mount info: %s\n", buf);
1275+		mount_uniq = strtok(buf, "\t");
1276+		path = mount_uniq ? strtok(NULL, "\r\n") : NULL;
1277+		if (!path) {
1278+			fprintf(stderr, "Failed to parse line from %s output\n", mount_helper_script);
1279+			exit_cleanup(RERR_SYNTAX);
1280+		}
1281+
1282+		if (lstat(path, &st) < 0) {
1283+			fprintf(stderr, "Failed to lstat(%s): %s\n", path, strerror(errno));
1284+			exit_cleanup(RERR_IPC);
1285+		}
1286+
1287+		bind_mount_uniq_len = strlcpy(bind_mount_uniq, mount_uniq, sizeof bind_mount_uniq);
1288+		if (bind_mount_uniq_len >= (int)sizeof bind_mount_uniq)
1289+			bind_mount_uniq_len = sizeof bind_mount_uniq - 1;
1290+
1291+		if (db_output_msgs) {
1292+			printf("Marking mount \"%s\" (%s) as a recent mount\n",
1293+				bind_mount_uniq, big_num(st.st_dev));
1294+		}
1295+		switch (use_db) {
1296+#ifdef USE_MYSQL
1297+		case DB_TYPE_MYSQL:
1298+			bind_devno = st.st_dev;
1299+			if (exec_mysql(INS_MOUNT) == NULL) {
1300+				fprintf(stderr, "Failed to update mount info for \"%s\" - %s\n",
1301+					bind_mount_uniq, mysql_error(dbh.mysql));
1302+				exit_cleanup(RERR_IPC);
1303+			}
1304+			break;
1305+#endif
1306+#ifdef USE_SQLITE
1307+		case DB_TYPE_SQLITE: {
1308+			int rc, change_cnt;
1309+			sqlite3_stmt *stmt = statements[INS_MOUNT].sqlite;
1310+			sqlite3_bind_text(stmt, 1, bind_thishost, bind_thishost_len, SQLITE_STATIC);
1311+			sqlite3_bind_int64(stmt, 2, bind_mtime);
1312+			sqlite3_bind_text(stmt, 3, bind_mount_uniq, bind_mount_uniq_len, SQLITE_STATIC);
1313+			sqlite3_bind_int64(stmt, 4, st.st_dev);
1314+			rc = sqlite3_step(stmt);
1315+			if (rc != SQLITE_DONE) {
1316+				fprintf(stderr, "Failed to insert mount info for \"%s\" - %s (%d)\n",
1317+					bind_mount_uniq, sqlite3_errmsg(dbh.sqlite), rc);
1318+				exit_cleanup(RERR_IPC);
1319+			}
1320+			change_cnt = sqlite3_changes(dbh.sqlite);
1321+			sqlite3_reset(stmt);
1322+			if (change_cnt == 0) {
1323+				stmt = statements[UPD_MOUNT].sqlite;
1324+				sqlite3_bind_int64(stmt, 1, bind_mtime);
1325+				sqlite3_bind_int64(stmt, 2, st.st_dev);
1326+				sqlite3_bind_text(stmt, 3, bind_thishost, bind_thishost_len, SQLITE_STATIC);
1327+				sqlite3_bind_text(stmt, 4, bind_mount_uniq, bind_mount_uniq_len, SQLITE_STATIC);
1328+				rc = sqlite3_step(stmt);
1329+				if (rc != SQLITE_DONE) {
1330+					fprintf(stderr, "Failed to update mount info for \"%s\" - %s (%d)\n",
1331+						bind_mount_uniq, sqlite3_errmsg(dbh.sqlite), rc);
1332+					exit_cleanup(RERR_IPC);
1333+				}
1334+				sqlite3_reset(stmt);
1335+			}
1336+			break;
1337+		    }
1338+#endif
1339+		}
1340+	}
1341+	close(f_from);
1342+
1343+	waitpid(pid, &status, 0);
1344+
1345+	switch (use_db) {
1346+#ifdef USE_MYSQL
1347+	case DB_TYPE_MYSQL: {
1348+		if (db_output_msgs) {
1349+			MYSQL_BIND binds[1];
1350+			MYSQL_STMT *stmt;
1351+
1352+			binds[0].buffer_type = MYSQL_TYPE_BLOB;
1353+			binds[0].buffer = bind_mount_uniq;
1354+			binds[0].buffer_length = sizeof bind_mount_uniq;
1355+			if (fetch_mysql(binds, 1, SEL_MOUNT, 1, &stmt)) {
1356+				while (1) {
1357+					printf("Marking mount \"%s\" as unmounted.\n", bind_mount_uniq);
1358+					if (mysql_stmt_fetch(stmt) != 0)
1359+						break;
1360+				}
1361+				mysql_stmt_free_result(stmt);
1362+			}
1363+		}
1364+
1365+		if (exec_mysql(UN_MOUNT) == NULL) {
1366+			fprintf(stderr, "Failed to update old mount info - %s\n",
1367+				mysql_error(dbh.mysql));
1368+			exit_cleanup(RERR_IPC);
1369+		}
1370+		break;
1371+	    }
1372+#endif
1373+#ifdef USE_SQLITE
1374+	case DB_TYPE_SQLITE: {
1375+		sqlite3_stmt *stmt;
1376+		int rc;
1377+
1378+		if (db_output_msgs) {
1379+			stmt = statements[SEL_MOUNT].sqlite;
1380+			sqlite3_bind_text(stmt, 1, bind_thishost, bind_thishost_len, SQLITE_STATIC);
1381+			sqlite3_bind_int64(stmt, 2, bind_mtime);
1382+			while (1) {
1383+				if (sqlite3_step(stmt) != SQLITE_ROW)
1384+					break;
1385+				printf("Marking mount \"%s\" as unmounted.\n", sqlite3_column_text(stmt, 0));
1386+			}
1387+			sqlite3_reset(stmt);
1388+		}
1389+
1390+		stmt = statements[UN_MOUNT].sqlite;
1391+		sqlite3_bind_text(stmt, 1, bind_thishost, bind_thishost_len, SQLITE_STATIC);
1392+		sqlite3_bind_int64(stmt, 2, bind_mtime);
1393+		rc = sqlite3_step(stmt);
1394+		sqlite3_reset(stmt);
1395+		if (rc != SQLITE_DONE) {
1396+			fprintf(stderr, "Failed to update old mount info - %s (%d)\n",
1397+				sqlite3_errmsg(dbh.sqlite), rc);
1398+			exit_cleanup(RERR_IPC);
1399+		}
1400+		break;
1401+	    }
1402+#endif
1403+	}
1404+}
1405+#endif
1406+
1407+static unsigned int get_disk_id(int64 devno)
1408+{
1409+	static unsigned int prior_disk_id = 0;
1410+	static int64 prior_devno = 0;
1411+
1412+	if (prior_devno == devno && prior_disk_id) {
1413+		if (DEBUG_GTE(DB, 5))
1414+			rprintf(FCLIENT, "get_disk_id(%s,%s) = %d (cached)\n", bind_thishost, big_num(devno), prior_disk_id);
1415+		return prior_disk_id;
1416+	}
1417+	prior_devno = devno;
1418+
1419+	switch (use_db) {
1420+#ifdef USE_MYSQL
1421+	case DB_TYPE_MYSQL: {
1422+		MYSQL_BIND binds[1];
1423+
1424+		bind_devno = devno; /* The one changing SEL_DEV input value. */
1425+
1426+		/* Bind where to put the output. */
1427+		binds[0].buffer_type = MYSQL_TYPE_LONG;
1428+		binds[0].buffer = &prior_disk_id;
1429+		if (!fetch_mysql(binds, 1, SEL_DEV, 1, NULL))
1430+			prior_disk_id = 0;
1431+		break;
1432+	    }
1433+#endif
1434+#ifdef USE_SQLITE
1435+	case DB_TYPE_SQLITE: {
1436+		sqlite3_stmt *stmt = statements[SEL_DEV].sqlite;
1437+		sqlite3_bind_text(stmt, 1, bind_thishost, bind_thishost_len, SQLITE_STATIC);
1438+		sqlite3_bind_int64(stmt, 2, devno);
1439+		if (sqlite3_step(stmt) == SQLITE_ROW)
1440+			prior_disk_id = sqlite3_column_int(stmt, 0);
1441+		else
1442+			prior_disk_id = 0;
1443+		sqlite3_reset(stmt);
1444+		break;
1445+	    }
1446+#endif
1447+	}
1448+
1449+	if (DEBUG_GTE(DB, 2))
1450+		rprintf(FCLIENT, "get_disk_id(%s,%s) = %d\n", bind_thishost, big_num(devno), prior_disk_id);
1451+	return prior_disk_id;
1452+}
1453+
1454+int db_get_checksum(const STRUCT_STAT *st_p, char *sum)
1455+{
1456+	unsigned int disk_id = get_disk_id(st_p->st_dev);
1457+	int ok = 0;
1458+
1459+	if (disk_id == 0)
1460+		return 0;
1461+
1462+	switch (use_db) {
1463+#ifdef USE_MYSQL
1464+	case DB_TYPE_MYSQL: {
1465+		MYSQL_BIND binds[1];
1466+
1467+		bind_disk_id = disk_id;
1468+		bind_ino = st_p->st_ino;
1469+		bind_size = st_p->st_size;
1470+		bind_mtime = st_p->st_mtime;
1471+		if (!db_lax)
1472+			bind_ctime = st_p->st_ctime;
1473+
1474+		binds[0].buffer_type = MYSQL_TYPE_BLOB;
1475+		binds[0].buffer = sum;
1476+		binds[0].buffer_length = MD5_DIGEST_LEN;
1477+		ok = fetch_mysql(binds, 1, SEL_SUM, 1, NULL);
1478+		break;
1479+	    }
1480+#endif
1481+#ifdef USE_SQLITE
1482+	case DB_TYPE_SQLITE: {
1483+		sqlite3_stmt *stmt = statements[SEL_SUM].sqlite;
1484+		sqlite3_bind_int(stmt, 1, disk_id);
1485+		sqlite3_bind_int64(stmt, 2, st_p->st_ino);
1486+		sqlite3_bind_int64(stmt, 3, st_p->st_size);
1487+		sqlite3_bind_int64(stmt, 4, st_p->st_mtime);
1488+		if (!db_lax)
1489+			sqlite3_bind_int64(stmt, 5, st_p->st_ctime);
1490+		if (sqlite3_step(stmt) == SQLITE_ROW) {
1491+			int len = sqlite3_column_bytes(stmt, 0);
1492+			if (len > MAX_DIGEST_LEN)
1493+				len = MAX_DIGEST_LEN;
1494+			memcpy(sum, sqlite3_column_blob(stmt, 0), len);
1495+			ok = 1;
1496+		}
1497+		sqlite3_reset(stmt);
1498+		break;
1499+	    }
1500+#endif
1501+	}
1502+
1503+	if (DEBUG_GTE(DB, 2)) {
1504+		if (ok) {
1505+			rprintf(FCLIENT, "[%s] Found DB checksum for %s,%s,%d: %s\n",
1506+				who_am_i(), big_num(st_p->st_dev),
1507+				big_num(st_p->st_ino), md_num, sum_as_hex(md_num, sum, 0));
1508+		} else {
1509+			rprintf(FCLIENT, "[%s] No DB checksum for %s,%s,%d\n",
1510+				who_am_i(), big_num(st_p->st_dev),
1511+				big_num(st_p->st_ino), md_num);
1512+		}
1513+	}
1514+
1515+	return ok;
1516+}
1517+
1518+int db_get_both_checksums(const STRUCT_STAT *st_p, int *right_sum_cnt, int *wrong_sum_cnt, char **sum4, char **sum5)
1519+{
1520+	static char dbsum[MD5_DIGEST_LEN*2];
1521+	int rows, j, sum_type[2];
1522+	int64 dbsize[2], dbmtime[2], dbctime[2];
1523+	unsigned int disk_id = get_disk_id(st_p->st_dev);
1524+
1525+	if (disk_id == 0)
1526+		return 0;
1527+
1528+	switch (use_db) {
1529+#ifdef USE_MYSQL
1530+	case DB_TYPE_MYSQL: {
1531+		MYSQL_BIND binds[5];
1532+
1533+		bind_disk_id = disk_id;
1534+		bind_ino = st_p->st_ino;
1535+
1536+		binds[0].buffer_type = MYSQL_TYPE_BLOB;
1537+		binds[0].buffer = dbsum;
1538+		binds[0].buffer_length = MD5_DIGEST_LEN;
1539+		binds[1].buffer_type = MYSQL_TYPE_LONG;
1540+		binds[1].buffer = (char*)sum_type;
1541+		binds[2].buffer_type = MYSQL_TYPE_LONGLONG;
1542+		binds[2].buffer = (char*)dbsize;
1543+		binds[3].buffer_type = MYSQL_TYPE_LONGLONG;
1544+		binds[3].buffer = (char*)dbmtime;
1545+		binds[4].buffer_type = MYSQL_TYPE_LONGLONG;
1546+		binds[4].buffer = (char*)dbctime;
1547+		rows = fetch_mysql(binds, 5, SEL_SUM, 2, NULL);
1548+		break;
1549+	    }
1550+#endif
1551+#ifdef USE_SQLITE
1552+	case DB_TYPE_SQLITE: {
1553+		sqlite3_stmt *stmt = statements[SEL_SUM].sqlite;
1554+		sqlite3_bind_int(stmt, 1, disk_id);
1555+		sqlite3_bind_int64(stmt, 2, st_p->st_ino);
1556+		for (j = 0; j < 2; j++) {
1557+			int len;
1558+			if (sqlite3_step(stmt) != SQLITE_ROW)
1559+				break;
1560+			len = sqlite3_column_bytes(stmt, 0);
1561+			if (len > MD5_DIGEST_LEN)
1562+				len = MD5_DIGEST_LEN;
1563+			memcpy(dbsum + MD5_DIGEST_LEN*j, sqlite3_column_blob(stmt, 0), len);
1564+			sum_type[j] = sqlite3_column_int(stmt, 1);
1565+			dbsize[j] = sqlite3_column_int(stmt, 2);
1566+			dbmtime[j] = sqlite3_column_int64(stmt, 3);
1567+			dbctime[j] = sqlite3_column_int64(stmt, 4);
1568+		}
1569+		sqlite3_reset(stmt);
1570+		rows = j;
1571+		break;
1572+	    }
1573+#endif
1574+	default:
1575+		return 0;
1576+	}
1577+
1578+	if (sum4)
1579+		*sum4 = NULL;
1580+	if (sum5)
1581+		*sum5 = NULL;
1582+	*right_sum_cnt = *wrong_sum_cnt = 0;
1583+	for (j = 0; j < rows; j++) {
1584+		if (DEBUG_GTE(DB, 3)) {
1585+			rprintf(FCLIENT, "DB checksum for %s,%s,%d: %s\n",
1586+				big_num(st_p->st_dev), big_num(st_p->st_ino), sum_type[j],
1587+				sum_as_hex(sum_type[j], dbsum + MD5_DIGEST_LEN*j, 0));
1588+		}
1589+
1590+		if (sum_type[j] == 4) {
1591+			if (!sum4)
1592+				continue;
1593+			*sum4 = dbsum + MD5_DIGEST_LEN*j;
1594+		} else {
1595+			if (!sum5)
1596+				continue;
1597+			*sum5 = dbsum + MD5_DIGEST_LEN*j;
1598+		}
1599+		if (st_p->st_size == dbsize[j] && st_p->st_mtime == dbmtime[j] && (db_lax || st_p->st_ctime == dbctime[j]))
1600+			++*right_sum_cnt;
1601+		else
1602+			++*wrong_sum_cnt;
1603+	}
1604+
1605+	return rows;
1606+}
1607+
1608+int db_set_checksum(int mdnum, const STRUCT_STAT *st_p, const char *sum)
1609+{
1610+	unsigned int disk_id;
1611+	const char *errmsg = NULL;
1612+	int rc = 0;
1613+
1614+	if (am_receiver || (am_generator && same_db)) {
1615+		/* Forward the setting to a single process.  The receiver always
1616+		 * forwards to the generator, and the generator will forward to
1617+		 * the sender ONLY if this is a local transfer. */
1618+		char data[MSG_CHECKSUM_LEN];
1619+		SIVAL64(data, 0, st_p->st_dev);
1620+		SIVAL64(data, 8, st_p->st_ino);
1621+		SIVAL64(data, 16, st_p->st_size);
1622+		SIVAL64(data, 24, st_p->st_mtime);
1623+		SIVAL64(data, 32, st_p->st_ctime);
1624+#if MSG_CHECKSUM_LONGS != 5
1625+#error Fix the setting of checksum long values
1626+#endif
1627+		SIVAL(data, MSG_CHECKSUM_LONGS*8, mdnum);
1628+		memcpy(data + MSG_CHECKSUM_LONGS*8 + 4, sum, MAX_DIGEST_LEN);
1629+		return send_msg(MSG_CHECKSUM, data, sizeof data, 0);
1630+	}
1631+
1632+	if ((disk_id = get_disk_id(st_p->st_dev)) == 0)
1633+		return 0;
1634+
1635+	switch (use_db) {
1636+#ifdef USE_MYSQL
1637+	case DB_TYPE_MYSQL:
1638+		if (transaction_state == 0) {
1639+			if (!run_sql("BEGIN"))
1640+				return 0;
1641+			transaction_state = 1;
1642+		}
1643+
1644+		bind_disk_id = disk_id;
1645+		bind_ino = st_p->st_ino;
1646+		bind_mdnum = mdnum;
1647+		bind_size = st_p->st_size;
1648+		bind_mtime = st_p->st_mtime;
1649+		bind_ctime = st_p->st_ctime;
1650+		memcpy(bind_sum, sum, MD5_DIGEST_LEN);
1651+		if (exec_mysql(REP_SUM) == NULL)
1652+			errmsg = mysql_error(dbh.mysql);
1653+		break;
1654+#endif
1655+#ifdef USE_SQLITE
1656+	case DB_TYPE_SQLITE: {
1657+		sqlite3_stmt *stmt = statements[REP_SUM].sqlite;
1658+		int lock_failures = 0;
1659+
1660+		if (transaction_state == 0) {
1661+			if (!run_sql("BEGIN"))
1662+				return 0;
1663+			transaction_state = 1;
1664+		}
1665+
1666+		sqlite3_bind_int(stmt, 1, disk_id);
1667+		sqlite3_bind_int64(stmt, 2, st_p->st_ino);
1668+		sqlite3_bind_int(stmt, 3, mdnum);
1669+		sqlite3_bind_int64(stmt, 4, st_p->st_size);
1670+		sqlite3_bind_int64(stmt, 5, st_p->st_mtime);
1671+		sqlite3_bind_int64(stmt, 6, st_p->st_ctime);
1672+		sqlite3_bind_blob(stmt, 7, sum, MD5_DIGEST_LEN, SQLITE_TRANSIENT);
1673+		while (1) {
1674+			rc = sqlite3_step(stmt);
1675+			if (rc != SQLITE_BUSY && rc != SQLITE_LOCKED)
1676+				break;
1677+			if (++lock_failures > MAX_LOCK_FAILURES)
1678+				break;
1679+			sqlite3_reset(stmt);
1680+			msleep(LOCK_FAIL_MSLEEP);
1681+		}
1682+		if (rc != SQLITE_DONE)
1683+			errmsg = sqlite3_errmsg(dbh.sqlite);
1684+		sqlite3_reset(stmt);
1685+		break;
1686+	    }
1687+#endif
1688+	}
1689+
1690+	if (!errmsg) {
1691+		if (DEBUG_GTE(DB, 2)) {
1692+			rprintf(FCLIENT, "[%s] Set DB checksum for %s,%s,%d: %s\n",
1693+				who_am_i(), big_num(st_p->st_dev), big_num(st_p->st_ino),
1694+				md_num, sum_as_hex(md_num, sum, 0));
1695+		}
1696+	} else {
1697+		rprintf(log_code, "[%s] Failed to set checksum for %s,%s,%d: %s (%d) -- closing DB\n",
1698+			who_am_i(), big_num(st_p->st_dev), big_num(st_p->st_ino),
1699+			md_num, errmsg, rc);
1700+		db_disconnect(False);
1701+	}
1702+
1703+	return errmsg ? 0 : 1;
1704+}
1705+
1706+/* For a delayed-update copy, we set the checksum on the file when it was
1707+ * inside the partial-dir.  Since renaming the file changes its ctime, we need
1708+ * to update the ctime to its new value (we can skip this in db_lax mode). */
1709+int db_update_ctime(UNUSED(int mdnum), const STRUCT_STAT *st_p)
1710+{
1711+	unsigned int disk_id = get_disk_id(st_p->st_dev);
1712+
1713+	if (disk_id == 0)
1714+		return 0;
1715+
1716+	switch (use_db) {
1717+#ifdef USE_MYSQL
1718+	case DB_TYPE_MYSQL:
1719+		bind_ctime = st_p->st_ctime;
1720+		bind_disk_id = disk_id;
1721+		bind_ino = st_p->st_ino;
1722+		bind_mdnum = mdnum;
1723+		bind_size = st_p->st_size;
1724+		bind_mtime = st_p->st_mtime;
1725+		return exec_mysql(UPD_CTIME) != NULL;
1726+#endif
1727+#ifdef USE_SQLITE
1728+	case DB_TYPE_SQLITE: {
1729+		int rc;
1730+
1731+		sqlite3_stmt *stmt = statements[UPD_CTIME].sqlite;
1732+		if (stmt == NULL)
1733+			return 0;
1734+		sqlite3_bind_int64(stmt, 1, st_p->st_ctime);
1735+		sqlite3_bind_int(stmt, 2, disk_id);
1736+		sqlite3_bind_int64(stmt, 3, st_p->st_ino);
1737+		sqlite3_bind_int(stmt, 4, mdnum);
1738+		sqlite3_bind_int64(stmt, 5, st_p->st_size);
1739+		sqlite3_bind_int64(stmt, 6, st_p->st_mtime);
1740+		rc = sqlite3_step(stmt);
1741+		sqlite3_reset(stmt);
1742+		return rc == SQLITE_DONE;
1743+	    }
1744+#endif
1745+	}
1746+
1747+	return 0;
1748+}
1749+
1750+static int db_clean_init(void)
1751+{
1752+	switch (use_db) {
1753+#ifdef USE_MYSQL
1754+	case DB_TYPE_MYSQL: {
1755+		MYSQL_BIND binds[MAX_BIND_CNT];
1756+		char *sql;
1757+
1758+		mysql_query(dbh.mysql,
1759+			"CREATE TEMPORARY TABLE inode_present ("
1760+			" disk_id integer unsigned NOT NULL,"
1761+			" ino bigint unsigned NOT NULL,"
1762+			" PRIMARY KEY (disk_id,ino)"
1763+			") ENGINE=MEMORY"
1764+			);
1765+
1766+		sql="INSERT IGNORE INTO inode_present"
1767+		    " SET disk_id = ?, ino = ?";
1768+		memset(binds, 0, sizeof binds);
1769+		binds[0].buffer_type = MYSQL_TYPE_LONG;
1770+		binds[0].buffer = &bind_disk_id;
1771+		binds[1].buffer_type = MYSQL_TYPE_LONGLONG;
1772+		binds[1].buffer = &bind_ino;
1773+		if (!prepare_mysql(INS_PRESENT, binds, 2, sql))
1774+			exit_cleanup(RERR_SYNTAX);
1775+
1776+		sql="DELETE m.*"
1777+		    " FROM inode_map AS m"
1778+		    " LEFT JOIN inode_present AS p USING(disk_id, ino)"
1779+		    " JOIN disk AS d ON(m.disk_id = d.disk_id)"
1780+		    " WHERE host = ? AND devno != 0 AND p.disk_id IS NULL AND ctime < ?";
1781+		memset(binds, 0, sizeof binds);
1782+		binds[0].buffer_type = MYSQL_TYPE_STRING;
1783+		binds[0].buffer = &bind_thishost;
1784+		binds[0].buffer_length = bind_thishost_len;
1785+		binds[1].buffer_type = MYSQL_TYPE_LONGLONG;
1786+		binds[1].buffer = &bind_ctime;
1787+		if (!prepare_mysql(DEL_SUMS, binds, 2, sql))
1788+			exit_cleanup(RERR_SYNTAX);
1789+
1790+		return 1;
1791+	    }
1792+#endif
1793+#ifdef USE_SQLITE
1794+	case DB_TYPE_SQLITE: {
1795+		char *sql;
1796+		sql="ATTACH DATABASE '' AS aux1;"; /* Private temp DB, probably in-memory */
1797+		if (!run_sql(sql))
1798+			exit_cleanup(RERR_IPC);
1799+
1800+		sql="CREATE TABLE aux1.inode_present ("
1801+		    " disk_id integer NOT NULL,"
1802+		    " ino bigint NOT NULL,"
1803+		    " PRIMARY KEY (disk_id,ino)"
1804+		    ")";
1805+		if (!run_sql(sql))
1806+			exit_cleanup(RERR_IPC);
1807+
1808+		sql="INSERT OR IGNORE INTO aux1.inode_present"
1809+		    " (disk_id, ino)"
1810+		    " VALUES (?, ?)";
1811+		if (!prepare_sqlite(INS_PRESENT, sql))
1812+			exit_cleanup(RERR_IPC);
1813+
1814+		sql="DELETE FROM inode_map"
1815+		    " WHERE ROWID IN ("
1816+		    "  SELECT m.ROWID"
1817+		    "  FROM inode_map AS m"
1818+		    "  LEFT JOIN aux1.inode_present AS p USING(disk_id, ino)"
1819+		    "  JOIN disk AS d ON(m.disk_id = d.disk_id)"
1820+		    "  WHERE host = ? AND devno != 0 AND p.disk_id IS NULL AND ctime < ?"
1821+		    " )";
1822+		if (!prepare_sqlite(DEL_SUMS, sql))
1823+			exit_cleanup(RERR_IPC);
1824+
1825+		transaction_state = -1; /* bug work-around -- force transaction off when cleaning XXX */
1826+
1827+		return 1;
1828+	    }
1829+#endif
1830+	}
1831+
1832+	return 0;
1833+}
1834+
1835+static int db_note_present(UNUSED(int disk_id), UNUSED(int64 ino))
1836+{
1837+	switch (use_db) {
1838+#ifdef USE_MYSQL
1839+	case DB_TYPE_MYSQL:
1840+		bind_disk_id = disk_id;
1841+		bind_ino = ino;
1842+		return exec_mysql(INS_PRESENT) != NULL;
1843+#endif
1844+#ifdef USE_SQLITE
1845+	case DB_TYPE_SQLITE: {
1846+		int rc;
1847+		sqlite3_stmt *stmt = statements[INS_PRESENT].sqlite;
1848+		sqlite3_bind_int(stmt, 1, disk_id);
1849+		sqlite3_bind_int64(stmt, 2, ino);
1850+		rc = sqlite3_step(stmt);
1851+		sqlite3_reset(stmt);
1852+		return rc == SQLITE_DONE;
1853+	    }
1854+#endif
1855+	}
1856+
1857+	return 0;
1858+}
1859+
1860+/* This function requires the user to have populated all disk_id+inode pairs
1861+ * into the inode_present table. */
1862+static int db_clean_inodes(UNUSED(time_t start_time))
1863+{
1864+	int del_cnt = 0;
1865+
1866+	/* The extra ctime < start_time check ensures that brand-new checksums that
1867+	 * were added after the start of our cleaning run are not removed. */
1868+	switch (use_db) {
1869+#ifdef USE_MYSQL
1870+	case DB_TYPE_MYSQL: {
1871+		MYSQL_STMT *stmt;
1872+		bind_ctime = start_time;
1873+		stmt = exec_mysql(DEL_SUMS);
1874+		if (stmt != NULL)
1875+			del_cnt = mysql_affected_rows(dbh.mysql);
1876+		break;
1877+	    }
1878+#endif
1879+#ifdef USE_SQLITE
1880+	case DB_TYPE_SQLITE: {
1881+		int rc;
1882+		sqlite3_stmt *stmt = statements[DEL_SUMS].sqlite;
1883+		sqlite3_bind_text(stmt, 1, bind_thishost, bind_thishost_len, SQLITE_STATIC);
1884+		sqlite3_bind_int64(stmt, 2, start_time);
1885+		rc = sqlite3_step(stmt);
1886+		if (rc == SQLITE_DONE)
1887+			del_cnt = sqlite3_changes(dbh.sqlite);
1888+		sqlite3_reset(stmt);
1889+		break;
1890+	    }
1891+#endif
1892+	}
1893+
1894+	return del_cnt;
1895+}
1896+
1897+static int abs_path(char *buf, int bufsiz, const char *curdir, const char *dir)
1898+{
1899+	if (*dir == '/')
1900+		strlcpy(buf, dir, bufsiz);
1901+	else {
1902+		int len = snprintf(buf, bufsiz, "%s/%s", curdir, dir);
1903+		assert(len > 0); /* silence a compiler warning */
1904+	}
1905+
1906+	return clean_fname(buf, CFN_DROP_TRAILING_DOT_DIR | CFN_COLLAPSE_DOT_DOT_DIRS);
1907+}
1908+
1909+static struct name_list *new_name(const char *basename, const char *filename)
1910+{
1911+	struct name_list *n;
1912+	int blen = strlen(basename);
1913+	int slen = filename ? (int)strlen(filename) : -1;
1914+	int len = blen + 1 + slen;
1915+
1916+	if (len >= MAXPATHLEN) {
1917+		if (filename)
1918+			rprintf(FERROR, "Filename too long: %s/%s\n", basename, filename);
1919+		else
1920+			rprintf(FERROR, "Filename too long: %s\n", basename);
1921+		return NULL;
1922+	}
1923+
1924+	n = (struct name_list *)new_array(char, sizeof (struct name_list) + len);
1925+
1926+	memcpy(n->name, basename, blen);
1927+	if (filename) {
1928+		n->name[blen] = '/';
1929+		memcpy(n->name + 1 + blen, filename, slen);
1930+	}
1931+	n->name[len] = '\0';
1932+	n->next = NULL;
1933+
1934+	return n;
1935+}
1936+
1937+static int name_compare(const void *n1, const void *n2)
1938+{
1939+	struct name_list *p1 = *(struct name_list **)n1;
1940+	struct name_list *p2 = *(struct name_list **)n2;
1941+	return strcmp(p1->name, p2->name);
1942+}
1943+
1944+static struct name_list *get_sorted_names(const char *dir)
1945+{
1946+	struct name_list *add, **sortbuf, *names = NULL, *prior_name = NULL;
1947+	struct dirent *di;
1948+	int cnt = 0;
1949+	DIR *d;
1950+
1951+	if (!(d = opendir("."))) {
1952+		rprintf(FERROR, "Unable to opendir %s: %s\n", dir, strerror(errno));
1953+		return NULL;
1954+	}
1955+	while ((di = readdir(d)) != NULL) {
1956+		char *dname = d_name(di);
1957+		if (dname[0] == '.' && (dname[1] == '\0' || (dname[1] == '.' && dname[2] == '\0')))
1958+			continue;
1959+		if (!(add = new_name(dname, NULL)))
1960+			continue;
1961+		if (prior_name)
1962+			prior_name->next = add;
1963+		else
1964+			names = add;
1965+		prior_name = add;
1966+		cnt++;
1967+	}
1968+	closedir(d);
1969+
1970+	if (cnt) {
1971+		int j;
1972+
1973+		sortbuf = new_array(struct name_list *, cnt);
1974+		for (j = 0; j < cnt; j++) {
1975+			sortbuf[j] = names;
1976+			names = names->next;
1977+		}
1978+
1979+		qsort(sortbuf, cnt, PTR_SIZE, name_compare);
1980+
1981+		names = prior_name = NULL;
1982+		for (j = 0; j < cnt; j++) {
1983+			add = sortbuf[j];
1984+			if (prior_name)
1985+				prior_name->next = add;
1986+			else
1987+				names = add;
1988+			prior_name = add;
1989+		}
1990+
1991+		if (prior_name)
1992+			prior_name->next = NULL;
1993+		free(sortbuf);
1994+	}
1995+
1996+	return names;
1997+}
1998+
1999+static inline int sums_ne(const char *sum1, const char *sum2)
2000+{
2001+	return memcmp(sum1, sum2, MD5_DIGEST_LEN) != 0;
2002+}
2003+
2004+/* Returns 1 if there is a checksum change, else 0. */
2005+static int mention_file(const char *dir, const char *name, int right_cnt, int wrong_cnt,
2006+			const char *dbsum4, const char *dbsum5, const char *sum4, const char *sum5)
2007+{
2008+	char *info_str = wrong_cnt && !right_cnt ? "!i " : "   ";
2009+	char *md4_str = !db_do_md4 ? NULL : !dbsum4 ? "+4 " : !sum4 ? "?4 " : sums_ne(sum4, dbsum4) ? "!4 " : "   ";
2010+	char *md5_str = !db_do_md5 ? NULL : !dbsum5 ? "+5 " : !sum5 ? "?5 " : sums_ne(sum5, dbsum5) ? "!5 " : "   ";
2011+	int chg = *info_str != ' ' || (md4_str && *md4_str != ' ') || (md5_str && *md5_str != ' ');
2012+	if (chg || db_output_unchanged) {
2013+		if (db_output_info) {
2014+			fputs(info_str, stdout);
2015+			if (md4_str)
2016+				fputs(md4_str, stdout);
2017+			if (md5_str)
2018+				fputs(md5_str, stdout);
2019+		}
2020+		if (db_output_sum) {
2021+			if (db_do_md4)
2022+				printf("%s ", sum_as_hex(4, sum4, 0));
2023+			if (db_do_md5)
2024+				printf("%s ", sum_as_hex(5, sum5, 0));
2025+		}
2026+		if (db_output_name) {
2027+			if (db_output_sum)
2028+				putchar(' '); /* We want 2 spaces, like md5sum. */
2029+			if (*dir != '.' || dir[1]) {
2030+				fputs(dir, stdout);
2031+				putchar('/');
2032+			}
2033+			puts(name);
2034+		}
2035+	}
2036+
2037+	return chg;
2038+}
2039+
2040+NORETURN void run_dbonly(const char **args)
2041+{
2042+	char start_dir[MAXPATHLEN], dirbuf[MAXPATHLEN];
2043+	int need_sum_cnt, start_dir_len;
2044+	struct name_list *prior_dir;
2045+	struct name_list *names;
2046+	time_t clean_start = 0;
2047+	int exit_code = 0;
2048+
2049+	checksum_type = 5;
2050+
2051+	need_sum_cnt = db_do_md4 + db_do_md5;
2052+
2053+	if (!db_read_config(FERROR, db_config) || !db_connect(1))
2054+		exit_cleanup(RERR_FILEIO);
2055+
2056+	if (db_clean) {
2057+		clean_start = time(NULL);
2058+		db_clean_init();
2059+	}
2060+
2061+	if (getcwd(start_dir, sizeof start_dir - 1) == NULL) {
2062+		rsyserr(FERROR, errno, "getcwd()");
2063+		exit_cleanup(RERR_FILESELECT);
2064+	}
2065+	start_dir_len = strlen(start_dir);
2066+
2067+	if (args) {
2068+		prior_dir = NULL;
2069+		while (*args) {
2070+			struct name_list *add;
2071+			if (abs_path(dirbuf, sizeof dirbuf, start_dir, *args++) <= 0)
2072+				continue;
2073+			if (!(add = new_name(dirbuf, NULL)))
2074+				continue;
2075+			if (prior_dir)
2076+				prior_dir->next = add;
2077+			else
2078+				dirs_list = add;
2079+			prior_dir = add;
2080+		}
2081+	} else
2082+		dirs_list = new_name(start_dir, NULL);
2083+
2084+	prior_dir = NULL;
2085+	while (dirs_list) {
2086+		struct name_list *subdirs, *prior_subdir, *prior_name;
2087+		const char *dir = dirs_list->name;
2088+		const char *reldir = dir;
2089+
2090+		if (prior_dir)
2091+			free((void*)prior_dir);
2092+		prior_dir = dirs_list;
2093+		dirs_list = dirs_list->next;
2094+
2095+		if (strncmp(reldir, start_dir, start_dir_len) == 0) {
2096+			if (reldir[start_dir_len] == '\0')
2097+				reldir = ".";
2098+			else if (reldir[start_dir_len] == '/')
2099+				reldir += start_dir_len + 1;
2100+		}
2101+		if (db_output_dirs)
2102+			printf("... %s/ ...\n", reldir);
2103+
2104+		if (chdir(dir) < 0) {
2105+			rprintf(FERROR, "Unable to chdir to %s: %s\n", dir, strerror(errno));
2106+			continue;
2107+		}
2108+		if (!(names = get_sorted_names(dir)))
2109+			continue;
2110+
2111+		subdirs = prior_subdir = prior_name = NULL;
2112+		while (names) {
2113+			STRUCT_STAT st;
2114+			char *dbsum4, *sum4, sumbuf4[MD5_DIGEST_LEN];
2115+			char *dbsum5, *sum5, sumbuf5[MD5_DIGEST_LEN];
2116+			int right_sum_cnt, wrong_sum_cnt;
2117+			const char *name = names->name;
2118+			unsigned int disk_id;
2119+
2120+			if (prior_name)
2121+				free((void*)prior_name);
2122+			prior_name = names;
2123+			names = names->next;
2124+
2125+			dbsum4 = dbsum5 = sum4 = sum5 = NULL;
2126+
2127+			if (lstat(name, &st) < 0) {
2128+				rprintf(FERROR, "Failed to lstat(%s): %s\n", name, strerror(errno));
2129+				continue;
2130+			}
2131+			if (S_ISLNK(st.st_mode))
2132+				continue;
2133+			if (S_ISDIR(st.st_mode)) {
2134+				/* add optional excluding of things like /^(CVS|\.svn|\.git|\.bzr)$/; */
2135+				if (recurse) {
2136+					struct name_list *add = new_name(dir, name);
2137+					if (add) {
2138+						if (prior_subdir)
2139+							prior_subdir->next = add;
2140+						else
2141+							subdirs = add;
2142+						prior_subdir = add;
2143+					}
2144+				}
2145+				continue;
2146+			}
2147+			if (!S_ISREG(st.st_mode))
2148+				continue;
2149+
2150+			if (!(disk_id = get_disk_id(st.st_dev)))
2151+				continue;
2152+			if (db_clean) {
2153+				db_note_present(disk_id, st.st_ino);
2154+				if (!db_update && !db_check)
2155+					continue;
2156+			}
2157+			db_get_both_checksums(&st, &right_sum_cnt, &wrong_sum_cnt,
2158+					      db_do_md4 ? &dbsum4 : NULL, db_do_md5 ? &dbsum5 : NULL);
2159+
2160+			if (!db_check && right_sum_cnt == need_sum_cnt) {
2161+				mention_file(reldir, name, right_sum_cnt, wrong_sum_cnt, dbsum4, dbsum5, dbsum4, dbsum5);
2162+				continue;
2163+			}
2164+
2165+			if (db_update || (db_check && right_sum_cnt) || db_output_sum) {
2166+				uchar *data;
2167+				int32 remainder;
2168+				md_context m4;
2169+				MD5_CTX m5;
2170+				struct map_struct *buf;
2171+				OFF_T off, len = st.st_size;
2172+				int fd = do_open(name, O_RDONLY, 0);
2173+
2174+				if (fd < 0) {
2175+					rprintf(FERROR, "ERROR: unable to read %s: %s\n", name, strerror(errno));
2176+					continue;
2177+				}
2178+
2179+				if (db_do_md4)
2180+					mdfour_begin(&m4);
2181+				if (db_do_md5)
2182+					MD5_Init(&m5);
2183+
2184+				buf = map_file(fd, len, MAX_MAP_SIZE, CSUM_CHUNK);
2185+
2186+				for (off = 0; off + CSUM_CHUNK <= len; off += CSUM_CHUNK) {
2187+					data = (uchar*)map_ptr(buf, off, CSUM_CHUNK);
2188+					if (db_do_md4)
2189+						mdfour_update(&m4, data, CSUM_CHUNK);
2190+					if (db_do_md5)
2191+						MD5_Update(&m5, data, CSUM_CHUNK);
2192+				}
2193+
2194+				remainder = (int32)(len - off);
2195+				data = (uchar*)map_ptr(buf, off, remainder);
2196+				if (db_do_md4) {
2197+					mdfour_update(&m4, data, remainder);
2198+					mdfour_result(&m4, (uchar*)(sum4 = sumbuf4));
2199+				}
2200+				if (db_do_md5) {
2201+					MD5_Update(&m5, data, remainder);
2202+					MD5_Final((uchar*)(sum5 = sumbuf5), &m5);
2203+				}
2204+
2205+				close(fd);
2206+				unmap_file(buf);
2207+			}
2208+
2209+			int chg = mention_file(reldir, name, right_sum_cnt, wrong_sum_cnt, dbsum4, dbsum5, sum4, sum5);
2210+			if (!chg) {
2211+				/* Only db_check should get here... */
2212+			} else if (!db_update) {
2213+				exit_code = 1;
2214+			} else {
2215+				int fail = 0;
2216+				if (db_do_md4 && !db_set_checksum(4, &st, sum4))
2217+					fail = 1;
2218+				if (db_do_md5 && !db_set_checksum(5, &st, sum5))
2219+					fail = 1;
2220+				if (fail) {
2221+					fprintf(stderr, "Failed to set checksum on %s/%s\n", reldir, name);
2222+					exit_cleanup(RERR_FILEIO);
2223+				}
2224+			}
2225+		}
2226+		if (prior_name)
2227+			free((void*)prior_name);
2228+
2229+		if (recurse && subdirs) {
2230+			prior_subdir->next = dirs_list;
2231+			dirs_list = subdirs;
2232+		}
2233+	}
2234+	if (prior_dir)
2235+		free((void*)prior_dir);
2236+
2237+	if (db_clean) {
2238+		int rows = db_clean_inodes(clean_start);
2239+		if (db_output_msgs)
2240+			printf("Cleaned out %d old inode%s.\n", rows, rows == 1 ? "" : "s");
2241+	}
2242+
2243+	db_disconnect(True);
2244+	exit(exit_code);
2245+}
2246diff --git a/flist.c b/flist.c
2247--- a/flist.c
2248+++ b/flist.c
2249@@ -54,6 +54,7 @@ extern int preserve_devices;
2250 extern int preserve_specials;
2251 extern int delete_during;
2252 extern int missing_args;
2253+extern int use_db;
2254 extern int eol_nulls;
2255 extern int atimes_ndx;
2256 extern int crtimes_ndx;
2257@@ -1367,11 +1368,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
2258 		extra_len += EXTRA_LEN;
2259 #endif
2260
2261-	if (always_checksum && am_sender && S_ISREG(st.st_mode)) {
2262-		file_checksum(thisname, &st, tmp_sum);
2263-		if (sender_keeps_checksum)
2264-			extra_len += SUM_EXTRA_CNT * EXTRA_LEN;
2265-	}
2266+	if (sender_keeps_checksum && S_ISREG(st.st_mode))
2267+		extra_len += SUM_EXTRA_CNT * EXTRA_LEN;
2268
2269 #if EXTRA_ROUNDING > 0
2270 	if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
2271@@ -1460,8 +1458,12 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
2272 		return NULL;
2273 	}
2274
2275-	if (sender_keeps_checksum && S_ISREG(st.st_mode))
2276-		memcpy(F_SUM(file), tmp_sum, flist_csum_len);
2277+	if (always_checksum && am_sender && S_ISREG(st.st_mode)) {
2278+		if (!use_db || !db_get_checksum(&st, tmp_sum))
2279+			file_checksum(thisname, &st, tmp_sum);
2280+		if (sender_keeps_checksum)
2281+			memcpy(F_SUM(file), tmp_sum, flist_csum_len);
2282+	}
2283
2284 	if (unsort_ndx)
2285 		F_NDX(file) = stats.num_dirs;
2286@@ -2145,6 +2147,9 @@ void send_extra_file_list(int f, int at_least)
2287   finish:
2288 	if (io_error != save_io_error && protocol_version == 30 && !ignore_errors)
2289 		send_msg_int(MSG_IO_ERROR, io_error);
2290+
2291+	if (use_db && flist_eof)
2292+		db_disconnect(True);
2293 }
2294
2295 struct file_list *send_file_list(int f, int argc, char *argv[])
2296@@ -2168,6 +2173,13 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
2297 		     | (eol_nulls || reading_remotely ? RL_EOL_NULLS : 0);
2298 	int implied_dot_dir = 0;
2299
2300+	if (use_db) {
2301+		if (always_checksum)
2302+			db_connect(0); /* Will reset use_db on error. */
2303+		else
2304+			use_db = 0;
2305+	}
2306+
2307 	rprintf(FLOG, "building file list\n");
2308 	if (show_filelist_progress)
2309 		start_filelist_progress("building file list");
2310@@ -2511,6 +2523,9 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
2311 			rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i());
2312 	}
2313
2314+	if (use_db && (!inc_recurse || flist_eof))
2315+		db_disconnect(True);
2316+
2317 	return flist;
2318 }
2319
2320diff --git a/generator.c b/generator.c
2321--- a/generator.c
2322+++ b/generator.c
2323@@ -61,6 +61,7 @@ extern int ignore_non_existing;
2324 extern int want_xattr_optim;
2325 extern int modify_window;
2326 extern int inplace;
2327+extern int use_db;
2328 extern int append_mode;
2329 extern int make_backups;
2330 extern int csum_length;
2331@@ -610,7 +611,8 @@ int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st)
2332 	   of the file time to determine whether to sync */
2333 	if (always_checksum > 0 && S_ISREG(st->st_mode)) {
2334 		char sum[MAX_DIGEST_LEN];
2335-		file_checksum(fn, st, sum);
2336+		if (!use_db || !db_get_checksum(st, sum))
2337+			file_checksum(fn, st, sum);
2338 		return memcmp(sum, F_SUM(file), flist_csum_len) == 0;
2339 	}
2340
2341@@ -2268,6 +2270,13 @@ void generate_files(int f_out, const char *local_name)
2342 			: "enabled");
2343 	}
2344
2345+	if (use_db) {
2346+		if (always_checksum || (append_mode != 1 && protocol_version >= 30))
2347+			db_connect(0); /* Will reset use_db on error. */
2348+		else
2349+			use_db = 0;
2350+	}
2351+
2352 	dflt_perms = (ACCESSPERMS & ~orig_umask);
2353
2354 	do {
2355@@ -2393,6 +2402,9 @@ void generate_files(int f_out, const char *local_name)
2356 			wait_for_receiver();
2357 	}
2358
2359+	if (use_db)
2360+		db_disconnect(True);
2361+
2362 	info_levels[INFO_FLIST] = save_info_flist;
2363 	info_levels[INFO_PROGRESS] = save_info_progress;
2364
2365diff --git a/io.c b/io.c
2366--- a/io.c
2367+++ b/io.c
2368@@ -41,8 +41,10 @@ extern int am_server;
2369 extern int am_sender;
2370 extern int am_receiver;
2371 extern int am_generator;
2372+extern int local_server;
2373 extern int msgs2stderr;
2374 extern int inc_recurse;
2375+extern int same_db;
2376 extern int io_error;
2377 extern int batch_fd;
2378 extern int eol_nulls;
2379@@ -1492,6 +1494,32 @@ static void read_a_msg(void)
2380 		if (am_sender)
2381 			maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
2382 		break;
2383+	case MSG_CHECKSUM:
2384+		/* This receives some checksum info that we want to make a note of
2385+		 * (which allows a single process to do all the writing to the db). */
2386+		if (msg_bytes != MSG_CHECKSUM_LEN)
2387+			goto overflow;
2388+		raw_read_buf(data, MSG_CHECKSUM_LEN);
2389+		if (am_generator && same_db) {
2390+			iobuf.in_multiplexed = 1;
2391+			send_msg(MSG_CHECKSUM, data, MSG_CHECKSUM_LEN, 0);
2392+		} if (am_receiver || (am_sender && !local_server))
2393+			goto unexpected;
2394+		else {
2395+			/* The received data is a set of numbers followed by the checksum. */
2396+			STRUCT_STAT st;
2397+			st.st_dev = IVAL64(data, 0);
2398+			st.st_ino = IVAL64(data, 8);
2399+			st.st_size = IVAL64(data, 16);
2400+			st.st_mtime = IVAL64(data, 24);
2401+			st.st_ctime = IVAL64(data, 32);
2402+#if MSG_CHECKSUM_LONGS != 5
2403+#error Fix the parsing of checksum long values
2404+#endif
2405+			iobuf.in_multiplexed = 1;
2406+			db_set_checksum(IVAL(data, MSG_CHECKSUM_LONGS*8), &st, data + MSG_CHECKSUM_LONGS*8 + 4);
2407+		}
2408+		break;
2409 	case MSG_DELETED:
2410 		if (msg_bytes >= sizeof data)
2411 			goto overflow;
2412@@ -1643,6 +1671,7 @@ static void read_a_msg(void)
2413 		 * with a duplicate exit message. */
2414 		_exit_cleanup(val, __FILE__, 0 - __LINE__);
2415 	default:
2416+	unexpected:
2417 		rprintf(FERROR, "unexpected tag %d [%s%s]\n",
2418 			tag, who_am_i(), inc_recurse ? "/inc" : "");
2419 		exit_cleanup(RERR_STREAMIO);
2420diff --git a/main.c b/main.c
2421--- a/main.c
2422+++ b/main.c
2423@@ -39,6 +39,7 @@ extern int am_root;
2424 extern int am_server;
2425 extern int am_sender;
2426 extern int am_daemon;
2427+extern int am_dbadmin;
2428 extern int inc_recurse;
2429 extern int blocking_io;
2430 extern int always_checksum;
2431@@ -57,6 +58,7 @@ extern int copy_unsafe_links;
2432 extern int keep_dirlinks;
2433 extern int preserve_hard_links;
2434 extern int protocol_version;
2435+extern int always_checksum;
2436 extern int mkpath_dest_arg;
2437 extern int file_total;
2438 extern int recurse;
2439@@ -93,6 +95,7 @@ extern char *logfile_format;
2440 extern char *filesfrom_host;
2441 extern char *partial_dir;
2442 extern char *rsync_path;
2443+extern char *db_config;
2444 extern char *shell_cmd;
2445 extern char *password_file;
2446 extern char *backup_dir;
2447@@ -1241,6 +1244,9 @@ void start_server(int f_in, int f_out, int argc, char *argv[])
2448 	if (am_daemon && io_timeout && protocol_version >= 31)
2449 		send_msg_int(MSG_IO_TIMEOUT, io_timeout);
2450
2451+	if (db_config)
2452+		db_read_config(FERROR, db_config);
2453+
2454 	if (am_sender) {
2455 		keep_dirlinks = 0; /* Must be disabled on the sender. */
2456 		if (need_messages_from_generator)
2457@@ -1531,6 +1537,9 @@ static int start_client(int argc, char *argv[])
2458 	else
2459 		env_port = rsync_port;
2460
2461+	if (db_config)
2462+		db_read_config(FERROR, db_config);
2463+
2464 	if (daemon_connection < 0)
2465 		return start_socket_client(shell_machine, remote_argc, remote_argv, argc, argv);
2466
2467diff --git a/options.c b/options.c
2468--- a/options.c
2469+++ b/options.c
2470@@ -83,6 +83,7 @@ int am_root = 0; /* 0 = normal, 1 = root, 2 = --super, -1 = --fake-super */
2471 int am_server = 0;
2472 int am_sender = 0;
2473 int am_starting_up = 1;
2474+int am_dbadmin = 0;
2475 int relative_paths = -1;
2476 int implied_dirs = 1;
2477 int missing_args = 0; /* 0 = FERROR_XFER, 1 = ignore, 2 = delete */
2478@@ -96,6 +97,7 @@ int use_qsort = 0;
2479 char *files_from = NULL;
2480 int filesfrom_fd = -1;
2481 char *filesfrom_host = NULL;
2482+char *db_config = NULL;
2483 int eol_nulls = 0;
2484 int protect_args = -1;
2485 int human_readable = 1;
2486@@ -104,6 +106,9 @@ int mkpath_dest_arg = 0;
2487 int allow_inc_recurse = 1;
2488 int xfer_dirs = -1;
2489 int am_daemon = 0;
2490+int db_clean, db_check, db_do_md4, db_do_md5, db_update = 1, db_lax, db_init, db_mounts;
2491+int db_output_name, db_output_sum, db_output_info, db_output_unchanged, db_output_dirs, db_output_msgs;
2492+int saw_db_output_opt, saw_db_sum_opt;
2493 int connect_timeout = 0;
2494 int keep_partial = 0;
2495 int safe_symlinks = 0;
2496@@ -282,6 +287,7 @@ static struct output_struct debug_words[COUNT_DEBUG+1] = {
2497 	DEBUG_WORD(CHDIR, W_CLI|W_SRV, "Debug when the current directory changes"),
2498 	DEBUG_WORD(CONNECT, W_CLI, "Debug connection events (levels 1-2)"),
2499 	DEBUG_WORD(CMD, W_CLI, "Debug commands+options that are issued (levels 1-2)"),
2500+	DEBUG_WORD(DB, W_SND|W_REC, "Debug DB operations (levels 1-5)"),
2501 	DEBUG_WORD(DEL, W_REC, "Debug delete actions (levels 1-3)"),
2502 	DEBUG_WORD(DELTASUM, W_SND|W_REC, "Debug delta-transfer checksumming (levels 1-4)"),
2503 	DEBUG_WORD(DUP, W_REC, "Debug weeding of duplicate names"),
2504@@ -573,6 +579,7 @@ enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
2505       OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD,
2506       OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
2507       OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE,
2508+      OPT_NO_DB, OPT_DBONLY,
2509       OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR,
2510       OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS,
2511       OPT_STOP_AFTER, OPT_STOP_AT,
2512@@ -729,6 +736,10 @@ static struct poptOption long_options[] = {
2513   {"no-c",             0,  POPT_ARG_VAL,    &always_checksum, 0, 0, 0 },
2514   {"checksum-choice",  0,  POPT_ARG_STRING, &checksum_choice, 0, 0, 0 },
2515   {"cc",               0,  POPT_ARG_STRING, &checksum_choice, 0, 0, 0 },
2516+  {"db",               0,  POPT_ARG_STRING, &db_config, 0, 0, 0 },
2517+  {"no-db",            0,  POPT_ARG_NONE,   0, OPT_NO_DB, 0, 0 },
2518+  {"db-lax",           0,  POPT_ARG_VAL,    &db_lax, 1, 0, 0 },
2519+  {"no-db-lax",        0,  POPT_ARG_VAL,    &db_lax, 0, 0, 0 },
2520   {"block-size",      'B', POPT_ARG_STRING, 0, OPT_BLOCK_SIZE, 0, 0 },
2521   {"compare-dest",     0,  POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
2522   {"copy-dest",        0,  POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
2523@@ -825,6 +836,9 @@ static struct poptOption long_options[] = {
2524   {"dparam",           0,  POPT_ARG_STRING, 0, OPT_DAEMON, 0, 0 },
2525   {"detach",           0,  POPT_ARG_NONE,   0, OPT_DAEMON, 0, 0 },
2526   {"no-detach",        0,  POPT_ARG_NONE,   0, OPT_DAEMON, 0, 0 },
2527+  /* All the following options switch us into DB-admin option-parsing. */
2528+  {"db-help",          0,  POPT_ARG_NONE,   0, OPT_DBONLY, 0, 0 },
2529+  {"db-only",          0,  POPT_ARG_STRING, 0, OPT_DBONLY, 0, 0 },
2530   {0,0,0,0, 0, 0, 0}
2531 };
2532
2533@@ -853,6 +867,31 @@ static struct poptOption long_daemon_options[] = {
2534   {0,0,0,0, 0, 0, 0}
2535 };
2536
2537+static struct poptOption long_dbonly_options[] = {
2538+  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
2539+  {"check",           'c', POPT_ARG_NONE,   &db_check, 0, 0, 0},
2540+  {"clean",            0,  POPT_ARG_NONE,   &db_clean, 0, 0, 0},
2541+  {"db",               0,  POPT_ARG_STRING, &db_config, 0, 0, 0 },
2542+  {"db-only",          0,  POPT_ARG_STRING, &db_config, 0, 0, 0 },
2543+  {"db-lax",           0,  POPT_ARG_VAL,    &db_lax, 1, 0, 0 },
2544+  {"no-db-lax",        0,  POPT_ARG_VAL,    &db_lax, 0, 0, 0 },
2545+  {"info",             0,  POPT_ARG_STRING, 0, OPT_INFO, 0, 0 },
2546+  {"debug",            0,  POPT_ARG_STRING, 0, OPT_DEBUG, 0, 0 },
2547+  {"update",          'u', POPT_ARG_VAL,    &db_update, 1, 0, 0 },
2548+  {"no-update",       'N', POPT_ARG_VAL,    &db_update, 0, 0, 0 },
2549+  {"no-u",             0,  POPT_ARG_VAL,    &db_update, 0, 0, 0 },
2550+  {"output",          'o', POPT_ARG_STRING, 0, 'o', 0, 0 },
2551+  {"recursive",       'r', POPT_ARG_VAL,    &recurse, 1, 0, 0 },
2552+  {"no-recursive",     0,  POPT_ARG_VAL,    &recurse, 0, 0, 0 },
2553+  {"no-r",             0,  POPT_ARG_VAL,    &recurse, 0, 0, 0 },
2554+  {"sums",            's', POPT_ARG_STRING, 0, 's', 0, 0 },
2555+  {"init",             0,  POPT_ARG_NONE,   &db_init, 0, 0, 0 },
2556+  {"mounts",           0,  POPT_ARG_NONE,   &db_mounts, 0, 0, 0 },
2557+  {"quiet",           'q', POPT_ARG_NONE,   &quiet, 0, 0, 0 },
2558+  {"help",            'h', POPT_ARG_NONE,   0, 'h', 0, 0 },
2559+  {"db-help",          0,  POPT_ARG_NONE,   0, 'h', 0, 0 },
2560+  {0,0,0,0, 0, 0, 0}
2561+};
2562
2563 static char err_buf[200];
2564
2565@@ -978,6 +1017,8 @@ static void set_refuse_options(void)
2566 			parse_one_refuse_match(0, "iconv", list_end);
2567 #endif
2568 		parse_one_refuse_match(0, "log-file*", list_end);
2569+		parse_one_refuse_match(0, "db", list_end);
2570+		parse_one_refuse_match(0, "db-lax", list_end);
2571 	}
2572
2573 #ifndef SUPPORT_ATIMES
2574@@ -1285,6 +1326,102 @@ static void create_refuse_error(int which)
2575 		snprintf(err_buf + n, sizeof err_buf - n, " (-%c)\n", op->shortName);
2576 }
2577
2578+static NORETURN void parse_dbonly_args(int argc, const char **argv)
2579+{
2580+	poptContext pc = poptGetContext(RSYNC_NAME, argc, argv, long_dbonly_options, 0);
2581+	const char *arg;
2582+	int opt;
2583+
2584+	recurse = 1;
2585+	am_dbadmin = 1;
2586+
2587+	while ((opt = poptGetNextOpt(pc)) != -1) {
2588+		const char *cp;
2589+		switch (opt) {
2590+		case 'o':
2591+			for (cp = poptGetOptArg(pc); *cp; cp++) {
2592+				switch (toLower(cp)) {
2593+				case 'n':
2594+					db_output_name = 1;
2595+					break;
2596+				case 's':
2597+				case 'c':
2598+					db_output_sum = db_output_name = 1;
2599+					break;
2600+				case 'i':
2601+					db_output_info = db_output_name = 1;
2602+					break;
2603+				case 'u':
2604+					db_output_unchanged = db_output_name = 1;
2605+					break;
2606+				case 'd':
2607+					db_output_dirs = 1;
2608+					break;
2609+				}
2610+			}
2611+			saw_db_output_opt = 1;
2612+			break;
2613+
2614+		case 's':
2615+			for (cp = poptGetOptArg(pc); *cp; cp++) {
2616+				switch (*cp) {
2617+				case '4':
2618+					db_do_md4 = 1;
2619+					break;
2620+				case '5':
2621+					db_do_md5 = 1;
2622+					break;
2623+				}
2624+			}
2625+			saw_db_sum_opt = 1;
2626+			break;
2627+
2628+		case 'h':
2629+			dbonly_usage(FINFO);
2630+			exit_cleanup(0);
2631+
2632+		case OPT_INFO:
2633+			arg = poptGetOptArg(pc);
2634+			parse_output_words(info_words, info_levels, arg, USER_PRIORITY);
2635+			break;
2636+
2637+		case OPT_DEBUG:
2638+			arg = poptGetOptArg(pc);
2639+			parse_output_words(debug_words, debug_levels, arg, USER_PRIORITY);
2640+			break;
2641+
2642+		default:
2643+			rprintf(FERROR,
2644+				"rsyncdb: %s: %s\n",
2645+				poptBadOption(pc, POPT_BADOPTION_NOALIAS),
2646+				poptStrerror(opt));
2647+			goto dbonly_usage;
2648+		}
2649+	}
2650+
2651+	if (!db_config) {
2652+		rprintf(FERROR, "You must specify the --db=FILE option.\n");
2653+	  dbonly_usage:
2654+		rprintf(FERROR,
2655+			"(Type \"rsyncdb --help\" for assistance.)\n");
2656+		exit_cleanup(RERR_SYNTAX);
2657+	}
2658+
2659+	if (!saw_db_output_opt && !quiet) {
2660+		db_output_dirs = db_output_name = 1;
2661+		if (db_check)
2662+			db_output_info = 1;
2663+	}
2664+	if (!quiet)
2665+		db_output_msgs = 1;
2666+	if (!saw_db_sum_opt)
2667+		db_do_md5 = 1;
2668+
2669+	am_starting_up = 0;
2670+	run_dbonly(poptGetArgs(pc));
2671+	exit(42); /* NOT REACHED */
2672+}
2673+
2674 /* This is used to make sure that --daemon & --server cannot be aliased to
2675  * something else. These options have always disabled popt aliases for the
2676  * parsing of a daemon or server command-line, but we have to make sure that
2677@@ -1341,6 +1478,12 @@ int parse_arguments(int *argc_p, const char ***argv_p)
2678 		return 0;
2679 	}
2680
2681+	arg = *argv + strlen(*argv);
2682+	if (arg - *argv > 2 && strcmp(arg-2, "db") == 0) {
2683+		parse_dbonly_args(argc, argv);
2684+		/* NOT REACHED */
2685+	}
2686+
2687 	set_refuse_options();
2688
2689 #ifdef ICONV_OPTION
2690@@ -1459,6 +1602,12 @@ int parse_arguments(int *argc_p, const char ***argv_p)
2691 			am_daemon = 1;
2692 			return 1;
2693
2694+		case OPT_DBONLY:
2695+			protect_args = 0;
2696+			poptFreeContext(pc);
2697+			parse_dbonly_args(argc, argv);
2698+			break; /* NOT REACHED */
2699+
2700 		case OPT_MODIFY_WINDOW:
2701 			/* The value has already been set by popt, but
2702 			 * we need to remember that we're using a
2703@@ -1531,6 +1680,10 @@ int parse_arguments(int *argc_p, const char ***argv_p)
2704 			preserve_devices = preserve_specials = 0;
2705 			break;
2706
2707+		case OPT_NO_DB:
2708+			db_config = NULL;
2709+			break;
2710+
2711 		case 'h':
2712 			human_readable++;
2713 			break;
2714diff --git a/pipe.c b/pipe.c
2715--- a/pipe.c
2716+++ b/pipe.c
2717@@ -27,11 +27,16 @@ extern int am_server;
2718 extern int blocking_io;
2719 extern int filesfrom_fd;
2720 extern int munge_symlinks;
2721+extern int always_checksum;
2722+extern int use_db;
2723+extern char *db_config;
2724 extern char *logfile_name;
2725 extern int remote_option_cnt;
2726 extern const char **remote_options;
2727 extern struct chmod_mode_struct *chmod_modes;
2728
2729+int same_db = 0;
2730+
2731 /**
2732  * Create a child connected to us via its stdin/stdout.
2733  *
2734@@ -141,13 +146,22 @@ pid_t local_child(int argc, char **argv, int *f_in, int *f_out,
2735 		}
2736
2737 		if (remote_option_cnt) {
2738+			const char *db_config_save = db_config;
2739 			int rc = remote_option_cnt + 1;
2740 			const char **rv = remote_options;
2741 			if (!parse_arguments(&rc, &rv)) {
2742 				option_error();
2743 				exit_cleanup(RERR_SYNTAX);
2744 			}
2745-		}
2746+			if (db_config == db_config_save)
2747+				same_db = db_config != NULL;
2748+			else if (!db_config || !db_config_save || strcmp(db_config, db_config_save) != 0) {
2749+				use_db = 0;
2750+				if (db_config)
2751+					db_read_config(FERROR, db_config);
2752+			}
2753+		} else if (use_db)
2754+			same_db = 1;
2755
2756 		if (dup2(to_child_pipe[0], STDIN_FILENO) < 0
2757 		 || close(to_child_pipe[1]) < 0
2758diff --git a/receiver.c b/receiver.c
2759--- a/receiver.c
2760+++ b/receiver.c
2761@@ -24,6 +24,8 @@
2762
2763 extern int dry_run;
2764 extern int do_xfers;
2765+extern int use_db;
2766+extern int db_lax;
2767 extern int am_root;
2768 extern int am_server;
2769 extern int inc_recurse;
2770@@ -433,6 +435,11 @@ static void handle_delayed_updates(char *local_name)
2771 					"rename failed for %s (from %s)",
2772 					full_fname(fname), partialptr);
2773 			} else {
2774+				if (use_db && !db_lax) {
2775+					STRUCT_STAT st;
2776+					if (do_lstat(fname, &st) == 0)
2777+						db_update_ctime(5, &st);
2778+				}
2779 				if (remove_source_files
2780 				 || (preserve_hard_links && F_IS_HLINKED(file)))
2781 					send_msg_int(MSG_SUCCESS, ndx);
2782@@ -539,6 +546,9 @@ int recv_files(int f_in, int f_out, char *local_name)
2783 	if (delay_updates)
2784 		delayed_bits = bitbag_create(cur_flist->used + 1);
2785
2786+	if (use_db && (append_mode == 1 || protocol_version < 30))
2787+		use_db = 0; /* We can't note finished md5 values */
2788+
2789 	progress_init();
2790
2791 	while (1) {
2792@@ -878,6 +888,8 @@ int recv_files(int f_in, int f_out, char *local_name)
2793 					do_unlink(partialptr);
2794 				handle_partial_dir(partialptr, PDIR_DELETE);
2795 			}
2796+			if (use_db && do_lstat(fname, &st) == 0)
2797+				db_set_checksum(5, &st, sender_file_sum);
2798 		} else if (keep_partial && partialptr && !one_inplace) {
2799 			if (!handle_partial_dir(partialptr, PDIR_CREATE)) {
2800 				rprintf(FERROR,
2801@@ -891,6 +903,8 @@ int recv_files(int f_in, int f_out, char *local_name)
2802 				recv_ok = -1;
2803 			else if (delay_updates && recv_ok) {
2804 				bitbag_set_bit(delayed_bits, ndx);
2805+				if (use_db && do_lstat(partialptr, &st) == 0)
2806+					db_set_checksum(5, &st, sender_file_sum);
2807 				recv_ok = 2;
2808 			} else
2809 				partialptr = NULL;
2810diff --git a/rsync.1.md b/rsync.1.md
2811--- a/rsync.1.md
2812+++ b/rsync.1.md
2813@@ -384,6 +384,9 @@ detailed description below for a complete description.
2814 --dry-run, -n            perform a trial run with no changes made
2815 --whole-file, -W         copy files whole (w/o delta-xfer algorithm)
2816 --checksum-choice=STR    choose the checksum algorithm (aka --cc)
2817+--db=CONFIG_FILE         specify a CONFIG_FILE for DB checksums
2818+--db-only=CONFIG_FILE    behave like rsyncdb
2819+--db-lax                 ignore ctime changes (use with CAUTION)
2820 --one-file-system, -x    don't cross filesystem boundaries
2821 --block-size=SIZE, -B    force a fixed checksum block-size
2822 --rsh=COMMAND, -e        specify the remote shell to use
2823diff --git a/rsync.c b/rsync.c
2824--- a/rsync.c
2825+++ b/rsync.c
2826@@ -39,6 +39,7 @@ extern int am_daemon;
2827 extern int am_sender;
2828 extern int am_receiver;
2829 extern int am_generator;
2830+extern int am_dbadmin;
2831 extern int am_starting_up;
2832 extern int allow_8bit_chars;
2833 extern int protocol_version;
2834@@ -807,6 +808,8 @@ struct file_list *flist_for_ndx(int ndx, const char *fatal_error_loc)
2835
2836 const char *who_am_i(void)
2837 {
2838+	if (am_dbadmin)
2839+		return "rsyncdb";
2840 	if (am_starting_up)
2841 		return am_server ? "server" : "client";
2842 	return am_sender ? "sender"
2843diff --git a/rsync.h b/rsync.h
2844--- a/rsync.h
2845+++ b/rsync.h
2846@@ -261,12 +261,16 @@ enum msgcode {
2847 	MSG_IO_ERROR=22,/* the sending side had an I/O error */
2848 	MSG_IO_TIMEOUT=33,/* tell client about a daemon's timeout value */
2849 	MSG_NOOP=42,	/* a do-nothing message (legacy protocol-30 only) */
2850+	MSG_CHECKSUM=55,/* sent via rcvr -> gen pipe and local-host-only gen -> sender */
2851 	MSG_ERROR_EXIT=86, /* synchronize an error exit (siblings and protocol >= 31) */
2852 	MSG_SUCCESS=100,/* successfully updated indicated flist index */
2853 	MSG_DELETED=101,/* successfully deleted a file on receiving side */
2854 	MSG_NO_SEND=102,/* sender failed to open a file we wanted */
2855 };
2856
2857+#define MSG_CHECKSUM_LONGS 5
2858+#define MSG_CHECKSUM_LEN (MSG_CHECKSUM_LONGS*8 + 4 + MAX_DIGEST_LEN)
2859+
2860 #define NDX_DONE -1
2861 #define NDX_FLIST_EOF -2
2862 #define NDX_DEL_STATS -3
2863@@ -1419,7 +1423,8 @@ extern short info_levels[], debug_levels[];
2864 #define DEBUG_CHDIR (DEBUG_BIND+1)
2865 #define DEBUG_CONNECT (DEBUG_CHDIR+1)
2866 #define DEBUG_CMD (DEBUG_CONNECT+1)
2867-#define DEBUG_DEL (DEBUG_CMD+1)
2868+#define DEBUG_DB (DEBUG_CMD+1)
2869+#define DEBUG_DEL (DEBUG_DB+1)
2870 #define DEBUG_DELTASUM (DEBUG_DEL+1)
2871 #define DEBUG_DUP (DEBUG_DELTASUM+1)
2872 #define DEBUG_EXIT (DEBUG_DUP+1)
2873diff --git a/rsyncdb-mountinfo b/rsyncdb-mountinfo
2874new file mode 100755
2875--- /dev/null
2876+++ b/rsyncdb-mountinfo
2877@@ -0,0 +1,82 @@
2878+#!/usr/bin/perl
2879+
2880+# This script outputs data for rsyncdb --mounts.  It must output a complete
2881+# list of the mounts for the current host in a strict format -- 2 fields
2882+# with a Tab between:  $MOUNT_UNIQ\t$PATH
2883+#
2884+# The list of mounts MUST NOT contain any entry that has the same devnum
2885+# (st_dev) as any other entry in the list (as checked via its PATH).
2886+#
2887+# MOUNT_UNIQ is a unique string that identifies the mount on this host.
2888+# This cannot be the devnum (st_dev) because that can vary depending on the
2889+# mount order or be reused for different mounts if they are not mounted at
2890+# the same time.  Ideally this would be its UUID value, if that is available
2891+# on this OS.  This script looks in /dev/disk/by-uuid for the current UUID
2892+# mappings).  If the UUID is not found, the fallback default is the string
2893+# "Mount of $devname", which should be adequate for situations that don't
2894+# use removable media (though you may need to take steps to weed-out removable
2895+# mounts).
2896+#
2897+# You can override the MOUNT_UNIQ value by putting a .rsyncdb_mount_uniq
2898+# file in the root directory of any mount, at which point it is up to you
2899+# to make sure that the value stays unique (note that all sequences of
2900+# whitespace are transformed into a single space, and leading/trailing
2901+# whitespace is removed).
2902+#
2903+# MOUNT_UNIQ may never contain a Tab but it would be legal for PATH to have
2904+# a Tab (just really weird).  Neither may have a CR or LF in it.
2905+#
2906+# The maximum size for MOUNT_UNIQ is 256 characters.
2907+#
2908+# If this script doesn't meet your needs, feel free to edit/replace it and
2909+# choose some other method of finding a unique value for each mount.  If you
2910+# come up with a good idiom that might be useful to others, please share it
2911+# with the rsync mailing list.
2912+
2913+use strict;
2914+use warnings;
2915+use Cwd 'abs_path';
2916+
2917+my @MOUNT_FILES = qw( /proc/mounts /etc/mtab );
2918+my $VALID_DEVICE_REGEX = qr{^/dev|^rootfs$};
2919+my $UUID_DIR = '/dev/disk/by-uuid';
2920+my $OVERRIDE_FILE = '.rsyncdb_mount_uniq';
2921+
2922+my (%hash, %uuid);
2923+
2924+if (-d $UUID_DIR) {
2925+    foreach my $uuid (glob "$UUID_DIR/*") {
2926+	my $lnk = readlink($uuid);
2927+	if ($lnk !~ m{^/}) {
2928+	    $lnk = abs_path("$UUID_DIR/$lnk");
2929+	}
2930+	$uuid =~ s{.*/}{};
2931+	$uuid{$lnk} = $uuid;
2932+    }
2933+}
2934+
2935+foreach my $mount_file (@MOUNT_FILES) {
2936+    if (open MOUNTS, $mount_file) {
2937+	while (<MOUNTS>) {
2938+	    my ($devname, $path) = (split)[0,1];
2939+	    next unless $devname =~ /$VALID_DEVICE_REGEX/;
2940+
2941+	    my ($devno) = (stat($path))[0];
2942+	    next unless defined $devno; # Skip if mount is invalid.
2943+	    next if $hash{$devno}++; # SKip if we've seen this devno earlier.
2944+
2945+	    my $mount_uniq = $uuid{$devname} ? $uuid{$devname} : "Mount of $devname";
2946+	    if (open UNIQ, '<', "$path/$OVERRIDE_FILE") {
2947+		$mount_uniq = <UNIQ>;
2948+		close UNIQ;
2949+		$mount_uniq =~ s/\s+/ /g; # This ensures no tab, CR, nor LF.
2950+		$mount_uniq =~ s/^ | $//g; # .. and no leading or trailing whitespace.
2951+	    }
2952+	    print $mount_uniq, "\t", $path, "\n";
2953+	}
2954+	close MOUNTS;
2955+	exit;
2956+    }
2957+}
2958+
2959+die "Failed to to open any mount files: @MOUNT_FILES\n";
2960diff --git a/rsyncdb.1.md b/rsyncdb.1.md
2961new file mode 100644
2962--- /dev/null
2963+++ b/rsyncdb.1.md
2964@@ -0,0 +1,217 @@
2965+# NAME
2966+
2967+rsyncdb - Maintain an rsync checksum DB
2968+
2969+# SYNOPSIS
2970+
2971+```
2972+rsyncdb --db=CONFIG [OPTION...] [DIR...]
2973+```
2974+
2975+# DESCRIPTION
2976+
2977+Rsyncdb can maintain a checksum-caching DB that rsync can use to make its
2978+`--checksum` option more optimal.  You must specify a config file via
2979+the `--db=CONFIG_FILE` option in order for rsyncdb to know what DB to
2980+manipulate.  See the rsync manpage's `--db` option for full details on
2981+the file's format.
2982+
2983+You can specify one or more directory args for rsyncdb to scan.  If no
2984+DIR args are specified, the current directory is assumed to be the spot
2985+to start scanning.
2986+
2987+Note that the rsyncdb program is usually just a symlink to the rsync program.
2988+You can force rsync to behave as rsyncdb either by having a symlink (or
2989+hardlink) name that ends with "db" or by `starting` the rsync args with
2990+`--db-only=CONFIG` (and that option works just like `--db=CONFIG` to
2991+a program named rsyncdb).
2992+
2993+# EXAMPLES
2994+
2995+The following command will update checksum information in the database
2996+described in the /etc/db.conf file:
2997+
2998+>     rsyncdb --db=/etc/db.conf -o n --clean /dir1 /dir2
2999+
3000+It scans 2 directory hierarchies (/dir1 & /dir2) and cleans out any
3001+checksums whose inodes are no longer found in those directories (so that
3002+directory args are presumed to be complete for this host's DB contents).
3003+
3004+The following command will scan all the files in the /dir2 directory (without
3005+recursive scanning, due to the `--no-r` option) and check them against
3006+the DB:
3007+
3008+>     rsyncdb --db=/etc/db.conf --check --no-r /dir2
3009+
3010+Any errors found are output as well as being fixed in the DB.  (See
3011+`--no-update` for how to check without updating.)
3012+
3013+The following command will output MD5 sums for all the files found in the
3014+directories mentioned, even if they are unchanged (due to the
3015+`--output=us` option):
3016+
3017+>     rsyncdb --db=/etc/db.conf -rous /dir* >/tmp/md5sums.txt
3018+
3019+This is just like running md5sum, only faster.  Unlike md5sum, you can't
3020+specify a single file, so use `--no-r` and grep the output if you just
3021+want to see a single file's value.
3022+
3023+The following command initializes a new DB, and is required for any new DB:
3024+
3025+>     rsyncdb --db=/etc/db.conf --init --mounts
3026+
3027+The `--init` option should only be used once (unless you want to
3028+destroy existing data).  The `--mounts` option may need to be used
3029+periodically, and makes use of a helper script (see below).
3030+
3031+# OPTIONS SUMMARY
3032+
3033+Rsyncdb accepts the following options:
3034+
3035+[comment]: # (help-rsyncdb.h)
3036+
3037+```
3038+--db=CONFIG       Specify the CONFIG file to read for the DB info
3039+--db-lax          Ignore ctime changes (use with CAUTION)
3040+--recursive, -r   Scan files in subdirs (the default w/o --no-recursive)
3041+--sums=SUMS, -s   List which checksums to update (default: 4,5)
3042+--output=STR, -o  One or more letters of what to output (default: "")
3043+--check, -c       Check checksums (by reading the files) and fix any
3044+                  issues.  Makes --output default to "dni".
3045+--clean           Note all inodes in the DIRS and remove DB extras
3046+--no-update, -N   Avoids updating/adding info w/--check and/or --clean
3047+--init            Initialize a DB by (re-)creating its tables
3048+--mounts          Scan for mounted filesystems and update the DB
3049+--quiet, -q       Disable the default non-error output
3050+--help, -h        Display this help message
3051+```
3052+
3053+# OPTIONS
3054+
3055+Rsyncdb accepts both long (double-dash + word) and short (single-dash + letter)
3056+options.  The full list of the available options are described below.  If an
3057+option can be specified in more than one way, the choices are comma-separated.
3058+Some options only have a long variant, not a short.  If the option takes a
3059+parameter, the parameter is only listed after the long variant, even though it
3060+must also be specified for the short.  When specifying a parameter, you can
3061+either use the form --option=param or replace the '=' with whitespace.  The
3062+parameter may need to be quoted in some manner for it to survive the shell's
3063+command-line parsing.
3064+
3065+0.  `--db=CONFIG_FILE`
3066+
3067+    This tells rsyncdb what DB-config file to read for the DB setup.  This is
3068+    the same as the option in rsync, so refer to that manpage for full details.
3069+
3070+0.  `--db-lax`
3071+
3072+    This option works just like it does in rsync, so refer to that manpage for
3073+    full details.
3074+
3075+0.  `--no-recursive, --no-r`
3076+
3077+    This disables the default recursive directory scan that is performed on the
3078+    listed directory args.  The options `--recursive` and `-r` are also
3079+    accepted, if someone wants to override an earlier `--no-r` override.
3080+
3081+0.  `--sums=SUMS, -s`
3082+
3083+    Only output/update the listed checksum types. By default we deal with just
3084+    the newer md5 checksums (i.e.  `--sums=5`).
3085+
3086+    Note that this option does NOT affect the order that checksums are output
3087+    if "-o s" is enabled, so `-s5,4` is the same as `-s4,5`.
3088+
3089+0.  `--output=STR, -o`
3090+
3091+    The output option lets you specify one or more letters indicating what
3092+    information should be output.  If `--output` is not specified, the default
3093+    is either "dn" or (with `--check`) "dni".
3094+
3095+    The following letters are accepted in the string:
3096+
3097+    - `d` outputs "... dir_name ..." lines for each directory in our scan.  if
3098+      "d" is omitted, then this progress indictor is not output.
3099+    - `n` includes the file's name in the per-file output. These lines are only
3100+      output for changed files unless "u" is given.  The "n" option is implied
3101+      by every other output option letter except "d".
3102+    - `s` includes the checksum info in the per-file output.
3103+    - `c` is a synonym for 's'.
3104+    - `i` includes itemized change info in the per-file output.
3105+      - `!i` indicates that the time and/or size is wrong.
3106+      - `+4` indicates the MD4 sum is missing.
3107+      - `+5` indicates the MD5 sum is missing.
3108+      - `!4` indicates the MD4 sum is wrong.
3109+      - `!5` indicates the MD5 sum is wrong.
3110+      - `?4` indicates an unknown MD4 difference.  This can happen if we didn't
3111+	need to read the file; i.e. if the time/size is wrong and no sum info
3112+	was requested.
3113+      - `?5` indicates an unknown MD5 difference.
3114+    - `u` includes unchanged files in the per-file output lines.
3115+
3116+0.  `--check, -c`
3117+
3118+    Check the checksums (forcing the reading of all the files) and fix any
3119+    issues that are found.  Makes `--output` default to "dni".
3120+
3121+0.  `--clean`
3122+
3123+    Makes a temp-DB of all the inodes that we find in all the listed
3124+    directories and removes any extraneous checksums from the DB.  You will
3125+    need to specify all the mounted directories that are present (and listed as
3126+    mounted) in the DB on this host or else the checksums from the unvisited
3127+    directories will be discarded from the DB.  If you want to just --clean
3128+    without adding or updating the info of new or changed files, specify
3129+    `--no-update` as well.
3130+
3131+0.  `--no-update, -N`
3132+
3133+    Avoids updating/adding info with `--check` and/or `--clean`.
3134+
3135+0.  `--quiet, -q`
3136+
3137+    Disable the default (non-error) output settings.  This turns off the
3138+    messages that `--init`, `--mount`, and `--clean` output, and makes the
3139+    default for `--output` be nothing (though an explicit `--output` option is
3140+    not affected).
3141+
3142+0.  `--init`
3143+
3144+    Create the tables in the DB.  If it is used on an existing DB, all the
3145+    existing tables are dropped and re-created.
3146+
3147+This option cannot be combined with the updating or reporting of checksum
3148+information, but may be combined with `--mounts`.
3149+
3150+0.  `--mounts`
3151+
3152+    Populate the "disk" DB with the available device numbers and change any
3153+    mounted/unmount information for devices.  This should be run every time a
3154+    mount-change happens that may affect a directory hierarchy in the DB.
3155+    Rsyncdb will not save any checksums for a device that is not listed in the
3156+    "disk" table.
3157+
3158+    The helper script "rsyncdb-mountinfo" is used as the source of the mount
3159+    information on the host, which it derives from various system files and
3160+    UUID directories (if available).  That script supports the use of an
3161+    override file named ".rsyncdb_mount_uniq" in the root of the mount as one
3162+    way to manually assign unique values to a shared (mountable) device's
3163+    various disks.
3164+
3165+    Some advanced users may want to maintain the disk table themselves in order
3166+    to support mounting a drive in different (or multiple) locations, etc.
3167+
3168+    Specifying the `--mounts` option cannot be combined with updating or
3169+    reporting of checksum information, but may be combined with `--init`.
3170+
3171+0.  `--help, -h`
3172+
3173+    Display a summary of the options.
3174+
3175+# SEE ALSO
3176+
3177+**rsync**(1)
3178+
3179+# AUTHOR
3180+
3181+Rsyncdb was written by Wayne Davison.
3182diff --git a/usage.c b/usage.c
3183--- a/usage.c
3184+++ b/usage.c
3185@@ -131,6 +131,16 @@ static void print_info_flags(enum logcode f)
3186 #endif
3187 			"crtimes",
3188
3189+#if !defined HAVE_MYSQL_MYSQL_H || !defined HAVE_LIBMYSQLCLIENT
3190+		"no "
3191+#endif
3192+			"MySQL",
3193+
3194+#if !defined HAVE_SQLITE3_H || !defined HAVE_LIBSQLITE3
3195+		"no "
3196+#endif
3197+			"SQLite",
3198+
3199 	"*Optimizations",
3200
3201 #ifndef HAVE_SIMD
3202@@ -250,6 +260,14 @@ void daemon_usage(enum logcode F)
3203   rprintf(F,"daemon-specific rsync options.  See also the rsyncd.conf(5) man page.\n");
3204 }
3205
3206+void dbonly_usage(enum logcode F)
3207+{
3208+  rprintf(F,"Usage: rsyncdb --db=CONFIG_FILE [OPTIONS] [DIRS]\n");
3209+  rprintf(F,"\n");
3210+  rprintf(F,"Options:\n");
3211+#include "help-rsyncdb.h"
3212+}
3213+
3214 const char *rsync_version(void)
3215 {
3216 	return RSYNC_GITVER;
3217diff -Nurp a/config.h.in b/config.h.in
3218--- a/config.h.in
3219+++ b/config.h.in
3220@@ -228,6 +228,9 @@
3221 /* Define to 1 if you have the `inet' library (-linet). */
3222 #undef HAVE_LIBINET
3223
3224+/* Define to 1 if you have the `mysqlclient' library (-lmysqlclient). */
3225+#undef HAVE_LIBMYSQLCLIENT
3226+
3227 /* Define to 1 if you have the `nsl' library (-lnsl). */
3228 #undef HAVE_LIBNSL
3229
3230@@ -246,6 +249,9 @@
3231 /* Define to 1 if you have the `socket' library (-lsocket). */
3232 #undef HAVE_LIBSOCKET
3233
3234+/* Define to 1 if you have the `sqlite3' library (-lsqlite3). */
3235+#undef HAVE_LIBSQLITE3
3236+
3237 /* Define to 1 if you have the `z' library (-lz). */
3238 #undef HAVE_LIBZ
3239
3240@@ -320,6 +326,9 @@
3241 /* Define to 1 if you have the `mtrace' function. */
3242 #undef HAVE_MTRACE
3243
3244+/* Define to 1 if you have the <mysql/mysql.h> header file. */
3245+#undef HAVE_MYSQL_MYSQL_H
3246+
3247 /* Define to 1 if you have the `nanosleep' function. */
3248 #undef HAVE_NANOSLEEP
3249
3250@@ -440,6 +449,15 @@
3251 /* True if you have Solaris xattrs */
3252 #undef HAVE_SOLARIS_XATTRS
3253
3254+/* Define to 1 if you have the <sqlite3.h> header file. */
3255+#undef HAVE_SQLITE3_H
3256+
3257+/* Define to 1 if you have the `sqlite3_open_v2' function. */
3258+#undef HAVE_SQLITE3_OPEN_V2
3259+
3260+/* Define to 1 if you have the `sqlite3_prepare_v2' function. */
3261+#undef HAVE_SQLITE3_PREPARE_V2
3262+
3263 /* Define to 1 if you have the <stdint.h> header file. */
3264 #undef HAVE_STDINT_H
3265
3266diff -Nurp a/configure.sh b/configure.sh
3267--- a/configure.sh
3268+++ b/configure.sh
3269@@ -625,6 +625,7 @@ ac_includes_default="\
3270
3271 ac_header_list=
3272 ac_subst_vars='LTLIBOBJS
3273+MYSQL_CONFIG
3274 MAKE_MAN
3275 BUILD_ZLIB
3276 BUILD_POPT
3277@@ -733,6 +734,8 @@ enable_iconv_open
3278 enable_iconv
3279 enable_acl_support
3280 enable_xattr_support
3281+enable_mysql
3282+enable_sqlite
3283 '
3284       ac_precious_vars='build_alias
3285 host_alias
3286@@ -1388,6 +1391,8 @@ Optional Features:
3287   --disable-iconv         disable rsync's --iconv option
3288   --disable-acl-support   disable ACL support
3289   --disable-xattr-support disable extended attributes
3290+  --disable-mysql         disable mysql DB support
3291+  --disable-sqlite        disable sqlite DB support
3292
3293 Optional Packages:
3294   --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
3295@@ -6249,6 +6254,7 @@ for ac_header in sys/fcntl.h sys/select.
3296     unistd.h utime.h grp.h compat.h sys/param.h ctype.h sys/wait.h \
3297     sys/ioctl.h sys/filio.h string.h stdlib.h sys/socket.h sys/mode.h \
3298     sys/un.h sys/attr.h mcheck.h arpa/inet.h arpa/nameser.h locale.h \
3299+    mysql/mysql.h sqlite3.h \
3300     netdb.h malloc.h float.h limits.h iconv.h libcharset.h langinfo.h \
3301     sys/acl.h acl/libacl.h attr/xattr.h sys/xattr.h sys/extattr.h dl.h \
3302     popt.h popt/popt.h linux/falloc.h netinet/in_systm.h netinet/ip.h \
3303@@ -10086,6 +10092,196 @@ $as_echo "$rsync_warn_flag" >&6; }
3304     fi
3305 fi
3306
3307+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to include mysql DB support" >&5
3308+$as_echo_n "checking whether to include mysql DB support... " >&6; }
3309+# Check whether --enable-mysql was given.
3310+if test "${enable_mysql+set}" = set; then :
3311+  enableval=$enable_mysql;
3312+fi
3313+
3314+
3315+if test x"$enable_mysql" = x"no"; then
3316+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
3317+$as_echo "no" >&6; }
3318+else
3319+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
3320+$as_echo "yes" >&6; }
3321+    # Extract the first word of "mysql_config", so it can be a program name with args.
3322+set dummy mysql_config; ac_word=$2
3323+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
3324+$as_echo_n "checking for $ac_word... " >&6; }
3325+if ${ac_cv_prog_MYSQL_CONFIG+:} false; then :
3326+  $as_echo_n "(cached) " >&6
3327+else
3328+  if test -n "$MYSQL_CONFIG"; then
3329+  ac_cv_prog_MYSQL_CONFIG="$MYSQL_CONFIG" # Let the user override the test.
3330+else
3331+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
3332+for as_dir in $PATH
3333+do
3334+  IFS=$as_save_IFS
3335+  test -z "$as_dir" && as_dir=.
3336+    for ac_exec_ext in '' $ac_executable_extensions; do
3337+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
3338+    ac_cv_prog_MYSQL_CONFIG="1"
3339+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
3340+    break 2
3341+  fi
3342+done
3343+  done
3344+IFS=$as_save_IFS
3345+
3346+  test -z "$ac_cv_prog_MYSQL_CONFIG" && ac_cv_prog_MYSQL_CONFIG="0"
3347+fi
3348+fi
3349+MYSQL_CONFIG=$ac_cv_prog_MYSQL_CONFIG
3350+if test -n "$MYSQL_CONFIG"; then
3351+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MYSQL_CONFIG" >&5
3352+$as_echo "$MYSQL_CONFIG" >&6; }
3353+else
3354+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
3355+$as_echo "no" >&6; }
3356+fi
3357+
3358+
3359+    if test x$MYSQL_CONFIG = x1; then
3360+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for mysql version >= 4" >&5
3361+$as_echo_n "checking for mysql version >= 4... " >&6; }
3362+	mysql_version=`mysql_config --version`
3363+	mysql_major_version=`echo $mysql_version | sed 's/\..*//'`
3364+	if test $mysql_major_version -lt 4; then
3365+	    { $as_echo "$as_me:${as_lineno-$LINENO}: result: no.. skipping MySQL" >&5
3366+$as_echo "no.. skipping MySQL" >&6; }
3367+	else
3368+	    { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
3369+$as_echo "yes" >&6; }
3370+
3371+	    MYSQL_CFLAGS=`mysql_config --cflags`
3372+	    MYSQL_LIBS=`mysql_config --libs`
3373+
3374+	    CPPFLAGS="$CPPFLAGS $MYSQL_CFLAGS"
3375+	    LIBS="$MYSQL_LIBS $LIBS"
3376+
3377+	    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for mysql_init in -lmysqlclient" >&5
3378+$as_echo_n "checking for mysql_init in -lmysqlclient... " >&6; }
3379+if ${ac_cv_lib_mysqlclient_mysql_init+:} false; then :
3380+  $as_echo_n "(cached) " >&6
3381+else
3382+  ac_check_lib_save_LIBS=$LIBS
3383+LIBS="-lmysqlclient  $LIBS"
3384+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
3385+/* end confdefs.h.  */
3386+
3387+/* Override any GCC internal prototype to avoid an error.
3388+   Use char because int might match the return type of a GCC
3389+   builtin and then its argument prototype would still apply.  */
3390+#ifdef __cplusplus
3391+extern "C"
3392+#endif
3393+char mysql_init ();
3394+int
3395+main ()
3396+{
3397+return mysql_init ();
3398+  ;
3399+  return 0;
3400+}
3401+_ACEOF
3402+if ac_fn_c_try_link "$LINENO"; then :
3403+  ac_cv_lib_mysqlclient_mysql_init=yes
3404+else
3405+  ac_cv_lib_mysqlclient_mysql_init=no
3406+fi
3407+rm -f core conftest.err conftest.$ac_objext \
3408+    conftest$ac_exeext conftest.$ac_ext
3409+LIBS=$ac_check_lib_save_LIBS
3410+fi
3411+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_mysqlclient_mysql_init" >&5
3412+$as_echo "$ac_cv_lib_mysqlclient_mysql_init" >&6; }
3413+if test "x$ac_cv_lib_mysqlclient_mysql_init" = xyes; then :
3414+  cat >>confdefs.h <<_ACEOF
3415+#define HAVE_LIBMYSQLCLIENT 1
3416+_ACEOF
3417+
3418+  LIBS="-lmysqlclient $LIBS"
3419+
3420+fi
3421+
3422+	fi
3423+    fi
3424+fi
3425+
3426+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to include sqlite DB support" >&5
3427+$as_echo_n "checking whether to include sqlite DB support... " >&6; }
3428+# Check whether --enable-sqlite was given.
3429+if test "${enable_sqlite+set}" = set; then :
3430+  enableval=$enable_sqlite;
3431+fi
3432+
3433+
3434+if test x"$enable_sqlite" = x"no"; then
3435+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
3436+$as_echo "no" >&6; }
3437+else
3438+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_open in -lsqlite3" >&5
3439+$as_echo_n "checking for sqlite3_open in -lsqlite3... " >&6; }
3440+if ${ac_cv_lib_sqlite3_sqlite3_open+:} false; then :
3441+  $as_echo_n "(cached) " >&6
3442+else
3443+  ac_check_lib_save_LIBS=$LIBS
3444+LIBS="-lsqlite3  $LIBS"
3445+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
3446+/* end confdefs.h.  */
3447+
3448+/* Override any GCC internal prototype to avoid an error.
3449+   Use char because int might match the return type of a GCC
3450+   builtin and then its argument prototype would still apply.  */
3451+#ifdef __cplusplus
3452+extern "C"
3453+#endif
3454+char sqlite3_open ();
3455+int
3456+main ()
3457+{
3458+return sqlite3_open ();
3459+  ;
3460+  return 0;
3461+}
3462+_ACEOF
3463+if ac_fn_c_try_link "$LINENO"; then :
3464+  ac_cv_lib_sqlite3_sqlite3_open=yes
3465+else
3466+  ac_cv_lib_sqlite3_sqlite3_open=no
3467+fi
3468+rm -f core conftest.err conftest.$ac_objext \
3469+    conftest$ac_exeext conftest.$ac_ext
3470+LIBS=$ac_check_lib_save_LIBS
3471+fi
3472+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sqlite3_sqlite3_open" >&5
3473+$as_echo "$ac_cv_lib_sqlite3_sqlite3_open" >&6; }
3474+if test "x$ac_cv_lib_sqlite3_sqlite3_open" = xyes; then :
3475+  cat >>confdefs.h <<_ACEOF
3476+#define HAVE_LIBSQLITE3 1
3477+_ACEOF
3478+
3479+  LIBS="-lsqlite3 $LIBS"
3480+
3481+fi
3482+
3483+    for ac_func in sqlite3_open_v2 sqlite3_prepare_v2
3484+do :
3485+  as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
3486+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
3487+if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
3488+  cat >>confdefs.h <<_ACEOF
3489+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
3490+_ACEOF
3491+
3492+fi
3493+done
3494+
3495+fi
3496+
3497 case "$CC" in
3498 ' checker'*|checker*)
3499
3500diff -Nurp a/rsync.1 b/rsync.1
3501--- a/rsync.1
3502+++ b/rsync.1
3503@@ -460,6 +460,9 @@ detailed description below for a complet
3504 --dry-run, -n            perform a trial run with no changes made
3505 --whole-file, -W         copy files whole (w/o delta-xfer algorithm)
3506 --checksum-choice=STR    choose the checksum algorithm (aka --cc)
3507+--db=CONFIG_FILE         specify a CONFIG_FILE for DB checksums
3508+--db-only=CONFIG_FILE    behave like rsyncdb
3509+--db-lax                 ignore ctime changes (use with CAUTION)
3510 --one-file-system, -x    don't cross filesystem boundaries
3511 --block-size=SIZE, -B    force a fixed checksum block-size
3512 --rsh=COMMAND, -e        specify the remote shell to use
3513diff -Nurp a/rsync.1.html b/rsync.1.html
3514--- a/rsync.1.html
3515+++ b/rsync.1.html
3516@@ -375,6 +375,9 @@ detailed description below for a complet
3517 --dry-run, -n            perform a trial run with no changes made
3518 --whole-file, -W         copy files whole (w/o delta-xfer algorithm)
3519 --checksum-choice=STR    choose the checksum algorithm (aka --cc)
3520+--db=CONFIG_FILE         specify a CONFIG_FILE for DB checksums
3521+--db-only=CONFIG_FILE    behave like rsyncdb
3522+--db-lax                 ignore ctime changes (use with CAUTION)
3523 --one-file-system, -x    don't cross filesystem boundaries
3524 --block-size=SIZE, -B    force a fixed checksum block-size
3525 --rsh=COMMAND, -e        specify the remote shell to use
3526diff -Nurp a/rsyncdb.1 b/rsyncdb.1
3527--- a/rsyncdb.1
3528+++ b/rsyncdb.1
3529@@ -0,0 +1,230 @@
3530+.TH "rsyncdb" "1" "06 Aug 2020" "rsyncdb 3.2.3" "User Commands"
3531+.P
3532+.SH "NAME"
3533+.P
3534+rsyncdb \- Maintain an rsync checksum DB
3535+.P
3536+.SH "SYNOPSIS"
3537+.P
3538+.nf
3539+rsyncdb --db=CONFIG [OPTION...] [DIR...]
3540+.fi
3541+.P
3542+.SH "DESCRIPTION"
3543+.P
3544+Rsyncdb can maintain a checksum-caching DB that rsync can use to make its
3545+\fB\-\-checksum\fP option more optimal.  You must specify a config file via
3546+the \fB\-\-db=CONFIG_FILE\fP option in order for rsyncdb to know what DB to
3547+manipulate.  See the rsync manpage's \fB\-\-db\fP option for full details on
3548+the file's format.
3549+.P
3550+You can specify one or more directory args for rsyncdb to scan.  If no
3551+DIR args are specified, the current directory is assumed to be the spot
3552+to start scanning.
3553+.P
3554+Note that the rsyncdb program is usually just a symlink to the rsync program.
3555+You can force rsync to behave as rsyncdb either by having a symlink (or
3556+hardlink) name that ends with "db" or by \fBstarting\fP the rsync args with
3557+\fB\-\-db-only=CONFIG\fP (and that option works just like \fB\-\-db=CONFIG\fP to
3558+a program named rsyncdb).
3559+.P
3560+.SH "EXAMPLES"
3561+.P
3562+The following command will update checksum information in the database
3563+described in the /etc/db.conf file:
3564+.RS 4
3565+.P
3566+.nf
3567+rsyncdb --db=/etc/db.conf -o n --clean /dir1 /dir2
3568+.fi
3569+.RE
3570+.P
3571+It scans 2 directory hierarchies (/dir1 & /dir2) and cleans out any
3572+checksums whose inodes are no longer found in those directories (so that
3573+directory args are presumed to be complete for this host's DB contents).
3574+.P
3575+The following command will scan all the files in the /dir2 directory (without
3576+recursive scanning, due to the \fB\-\-no-r\fP option) and check them against
3577+the DB:
3578+.RS 4
3579+.P
3580+.nf
3581+rsyncdb --db=/etc/db.conf --check --no-r /dir2
3582+.fi
3583+.RE
3584+.P
3585+Any errors found are output as well as being fixed in the DB.  (See
3586+\fB\-\-no-update\fP for how to check without updating.)
3587+.P
3588+The following command will output MD5 sums for all the files found in the
3589+directories mentioned, even if they are unchanged (due to the
3590+\fB\-\-output=us\fP option):
3591+.RS 4
3592+.P
3593+.nf
3594+rsyncdb --db=/etc/db.conf -rous /dir* >/tmp/md5sums.txt
3595+.fi
3596+.RE
3597+.P
3598+This is just like running md5sum, only faster.  Unlike md5sum, you can't
3599+specify a single file, so use \fB\-\-no-r\fP and grep the output if you just
3600+want to see a single file's value.
3601+.P
3602+The following command initializes a new DB, and is required for any new DB:
3603+.RS 4
3604+.P
3605+.nf
3606+rsyncdb --db=/etc/db.conf --init --mounts
3607+.fi
3608+.RE
3609+.P
3610+The \fB\-\-init\fP option should only be used once (unless you want to
3611+destroy existing data).  The \fB\-\-mounts\fP option may need to be used
3612+periodically, and makes use of a helper script (see below).
3613+.P
3614+.SH "OPTIONS SUMMARY"
3615+.P
3616+Rsyncdb accepts the following options:
3617+.P
3618+.nf
3619+--db=CONFIG       Specify the CONFIG file to read for the DB info
3620+--db-lax          Ignore ctime changes (use with CAUTION)
3621+--recursive, -r   Scan files in subdirs (the default w/o --no-recursive)
3622+--sums=SUMS, -s   List which checksums to update (default: 4,5)
3623+--output=STR, -o  One or more letters of what to output (default: "")
3624+--check, -c       Check checksums (by reading the files) and fix any
3625+                  issues.  Makes --output default to "dni".
3626+--clean           Note all inodes in the DIRS and remove DB extras
3627+--no-update, -N   Avoids updating/adding info w/--check and/or --clean
3628+--init            Initialize a DB by (re-)creating its tables
3629+--mounts          Scan for mounted filesystems and update the DB
3630+--quiet, -q       Disable the default non-error output
3631+--help, -h        Display this help message
3632+.fi
3633+.P
3634+.SH "OPTIONS"
3635+.P
3636+Rsyncdb accepts both long (double-dash + word) and short (single-dash + letter)
3637+options.  The full list of the available options are described below.  If an
3638+option can be specified in more than one way, the choices are comma-separated.
3639+Some options only have a long variant, not a short.  If the option takes a
3640+parameter, the parameter is only listed after the long variant, even though it
3641+must also be specified for the short.  When specifying a parameter, you can
3642+either use the form \-\-option=param or replace the '=' with whitespace.  The
3643+parameter may need to be quoted in some manner for it to survive the shell's
3644+command-line parsing.
3645+.P
3646+.IP "\fB\-\-db=CONFIG_FILE\fP"
3647+This tells rsyncdb what DB-config file to read for the DB setup.  This is
3648+the same as the option in rsync, so refer to that manpage for full details.
3649+.IP "\fB\-\-db-lax\fP"
3650+This option works just like it does in rsync, so refer to that manpage for
3651+full details.
3652+.IP "\fB\-\-no-recursive,\ \-\-no-r\fP"
3653+This disables the default recursive directory scan that is performed on the
3654+listed directory args.  The options \fB\-\-recursive\fP and \fB\-r\fP are also
3655+accepted, if someone wants to override an earlier \fB\-\-no-r\fP override.
3656+.IP "\fB\-\-sums=SUMS,\ \-s\fP"
3657+Only output/update the listed checksum types. By default we deal with just
3658+the newer md5 checksums (i.e.  \fB\-\-sums=5\fP).
3659+.IP
3660+Note that this option does NOT affect the order that checksums are output
3661+if "\-o s" is enabled, so \fB\-s5,4\fP is the same as \fB\-s4,5\fP.
3662+.IP "\fB\-\-output=STR,\ \-o\fP"
3663+The output option lets you specify one or more letters indicating what
3664+information should be output.  If \fB\-\-output\fP is not specified, the default
3665+is either "dn" or (with \fB\-\-check\fP) "dni".
3666+.IP
3667+The following letters are accepted in the string:
3668+.IP
3669+.RS
3670+.IP o
3671+\fBd\fP outputs "... dir_name ..." lines for each directory in our scan.  if
3672+"d" is omitted, then this progress indictor is not output.
3673+.IP o
3674+\fBn\fP includes the file's name in the per-file output. These lines are only
3675+output for changed files unless "u" is given.  The "n" option is implied
3676+by every other output option letter except "d".
3677+.IP o
3678+\fBs\fP includes the checksum info in the per-file output.
3679+.IP o
3680+\fBc\fP is a synonym for 's'.
3681+.IP o
3682+.IP
3683+.RS
3684+.IP o
3685+\fBi\fP includes itemized change info in the per-file output.
3686+
3687+\fB!i\fP indicates that the time and/or size is wrong.
3688+.IP o
3689+\fB+4\fP indicates the MD4 sum is missing.
3690+.IP o
3691+\fB+5\fP indicates the MD5 sum is missing.
3692+.IP o
3693+\fB!4\fP indicates the MD4 sum is wrong.
3694+.IP o
3695+\fB!5\fP indicates the MD5 sum is wrong.
3696+.IP o
3697+\fB?4\fP indicates an unknown MD4 difference.  This can happen if we didn't
3698+need to read the file; i.e. if the time/size is wrong and no sum info
3699+was requested.
3700+.IP o
3701+\fB?5\fP indicates an unknown MD5 difference.
3702+.RE
3703+.IP o
3704+\fBu\fP includes unchanged files in the per-file output lines.
3705+.RE
3706+.IP "\fB\-\-check,\ \-c\fP"
3707+Check the checksums (forcing the reading of all the files) and fix any
3708+issues that are found.  Makes \fB\-\-output\fP default to "dni".
3709+.IP "\fB\-\-clean\fP"
3710+Makes a temp-DB of all the inodes that we find in all the listed
3711+directories and removes any extraneous checksums from the DB.  You will
3712+need to specify all the mounted directories that are present (and listed as
3713+mounted) in the DB on this host or else the checksums from the unvisited
3714+directories will be discarded from the DB.  If you want to just \-\-clean
3715+without adding or updating the info of new or changed files, specify
3716+\fB\-\-no-update\fP as well.
3717+.IP "\fB\-\-no-update,\ \-N\fP"
3718+Avoids updating/adding info with \fB\-\-check\fP and/or \fB\-\-clean\fP.
3719+.IP "\fB\-\-quiet,\ \-q\fP"
3720+Disable the default (non-error) output settings.  This turns off the
3721+messages that \fB\-\-init\fP, \fB\-\-mount\fP, and \fB\-\-clean\fP output, and makes the
3722+default for \fB\-\-output\fP be nothing (though an explicit \fB\-\-output\fP option is
3723+not affected).
3724+.IP "\fB\-\-init\fP"
3725+Create the tables in the DB.  If it is used on an existing DB, all the
3726+existing tables are dropped and re-created.
3727+.P
3728+This option cannot be combined with the updating or reporting of checksum
3729+information, but may be combined with \fB\-\-mounts\fP.
3730+.P
3731+.IP "\fB\-\-mounts\fP"
3732+Populate the "disk" DB with the available device numbers and change any
3733+mounted/unmount information for devices.  This should be run every time a
3734+mount-change happens that may affect a directory hierarchy in the DB.
3735+Rsyncdb will not save any checksums for a device that is not listed in the
3736+"disk" table.
3737+.IP
3738+The helper script "rsyncdb-mountinfo" is used as the source of the mount
3739+information on the host, which it derives from various system files and
3740+UUID directories (if available).  That script supports the use of an
3741+override file named ".rsyncdb_mount_uniq" in the root of the mount as one
3742+way to manually assign unique values to a shared (mountable) device's
3743+various disks.
3744+.IP
3745+Some advanced users may want to maintain the disk table themselves in order
3746+to support mounting a drive in different (or multiple) locations, etc.
3747+.IP
3748+Specifying the \fB\-\-mounts\fP option cannot be combined with updating or
3749+reporting of checksum information, but may be combined with \fB\-\-init\fP.
3750+.IP "\fB\-\-help,\ \-h\fP"
3751+Display a summary of the options.
3752+.P
3753+.SH "SEE ALSO"
3754+.P
3755+\fBrsync\fP(1)
3756+.P
3757+.SH "AUTHOR"
3758+.P
3759+Rsyncdb was written by Wayne Davison.
3760diff -Nurp a/rsyncdb.1.html b/rsyncdb.1.html
3761--- a/rsyncdb.1.html
3762+++ b/rsyncdb.1.html
3763@@ -0,0 +1,228 @@
3764+<html><head>
3765+<title>rsyncdb(1) man page</title>
3766+<link href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Mono&display=swap" rel="stylesheet">
3767+<style>
3768+body {
3769+  max-width: 50em;
3770+  margin: auto;
3771+}
3772+body, b, strong, u {
3773+  font-family: 'Roboto', sans-serif;
3774+}
3775+code {
3776+  font-family: 'Roboto Mono', monospace;
3777+  font-weight: bold;
3778+  white-space: pre;
3779+}
3780+pre code {
3781+  display: block;
3782+  font-weight: normal;
3783+}
3784+blockquote pre code {
3785+  background: #f1f1f1;
3786+}
3787+dd p:first-of-type {
3788+  margin-block-start: 0em;
3789+}
3790+</style>
3791+</head><body>
3792+<h1>NAME</h1>
3793+<p>rsyncdb -&#8288; Maintain an rsync checksum DB</p>
3794+<h1>SYNOPSIS</h1>
3795+<pre><code>rsyncdb --db=CONFIG [OPTION...] [DIR...]
3796+</code></pre>
3797+<h1>DESCRIPTION</h1>
3798+<p>Rsyncdb can maintain a checksum-caching DB that rsync can use to make its
3799+<code>--checksum</code> option more optimal.  You must specify a config file via
3800+the <code>--db=CONFIG_FILE</code> option in order for rsyncdb to know what DB to
3801+manipulate.  See the rsync manpage's <code>--db</code> option for full details on
3802+the file's format.</p>
3803+<p>You can specify one or more directory args for rsyncdb to scan.  If no
3804+DIR args are specified, the current directory is assumed to be the spot
3805+to start scanning.</p>
3806+<p>Note that the rsyncdb program is usually just a symlink to the rsync program.
3807+You can force rsync to behave as rsyncdb either by having a symlink (or
3808+hardlink) name that ends with &quot;db&quot; or by <code>starting</code> the rsync args with
3809+<code>--db-only=CONFIG</code> (and that option works just like <code>--db=CONFIG</code> to
3810+a program named rsyncdb).</p>
3811+<h1>EXAMPLES</h1>
3812+<p>The following command will update checksum information in the database
3813+described in the /etc/db.conf file:</p>
3814+<blockquote>
3815+<pre><code>rsyncdb --db=/etc/db.conf -o n --clean /dir1 /dir2
3816+</code></pre>
3817+</blockquote>
3818+<p>It scans 2 directory hierarchies (/dir1 &amp; /dir2) and cleans out any
3819+checksums whose inodes are no longer found in those directories (so that
3820+directory args are presumed to be complete for this host's DB contents).</p>
3821+<p>The following command will scan all the files in the /dir2 directory (without
3822+recursive scanning, due to the <code>--no-r</code> option) and check them against
3823+the DB:</p>
3824+<blockquote>
3825+<pre><code>rsyncdb --db=/etc/db.conf --check --no-r /dir2
3826+</code></pre>
3827+</blockquote>
3828+<p>Any errors found are output as well as being fixed in the DB.  (See
3829+<code>--no-update</code> for how to check without updating.)</p>
3830+<p>The following command will output MD5 sums for all the files found in the
3831+directories mentioned, even if they are unchanged (due to the
3832+<code>--output=us</code> option):</p>
3833+<blockquote>
3834+<pre><code>rsyncdb --db=/etc/db.conf -rous /dir* &gt;/tmp/md5sums.txt
3835+</code></pre>
3836+</blockquote>
3837+<p>This is just like running md5sum, only faster.  Unlike md5sum, you can't
3838+specify a single file, so use <code>--no-r</code> and grep the output if you just
3839+want to see a single file's value.</p>
3840+<p>The following command initializes a new DB, and is required for any new DB:</p>
3841+<blockquote>
3842+<pre><code>rsyncdb --db=/etc/db.conf --init --mounts
3843+</code></pre>
3844+</blockquote>
3845+<p>The <code>--init</code> option should only be used once (unless you want to
3846+destroy existing data).  The <code>--mounts</code> option may need to be used
3847+periodically, and makes use of a helper script (see below).</p>
3848+<h1>OPTIONS SUMMARY</h1>
3849+<p>Rsyncdb accepts the following options:</p>
3850+<pre><code>--db=CONFIG       Specify the CONFIG file to read for the DB info
3851+--db-lax          Ignore ctime changes (use with CAUTION)
3852+--recursive, -r   Scan files in subdirs (the default w/o --no-recursive)
3853+--sums=SUMS, -s   List which checksums to update (default: 4,5)
3854+--output=STR, -o  One or more letters of what to output (default: &quot;&quot;)
3855+--check, -c       Check checksums (by reading the files) and fix any
3856+                  issues.  Makes --output default to &quot;dni&quot;.
3857+--clean           Note all inodes in the DIRS and remove DB extras
3858+--no-update, -N   Avoids updating/adding info w/--check and/or --clean
3859+--init            Initialize a DB by (re-)creating its tables
3860+--mounts          Scan for mounted filesystems and update the DB
3861+--quiet, -q       Disable the default non-error output
3862+--help, -h        Display this help message
3863+</code></pre>
3864+<h1>OPTIONS</h1>
3865+<p>Rsyncdb accepts both long (double-dash + word) and short (single-dash + letter)
3866+options.  The full list of the available options are described below.  If an
3867+option can be specified in more than one way, the choices are comma-separated.
3868+Some options only have a long variant, not a short.  If the option takes a
3869+parameter, the parameter is only listed after the long variant, even though it
3870+must also be specified for the short.  When specifying a parameter, you can
3871+either use the form -&#8288;-&#8288;option=param or replace the '=' with whitespace.  The
3872+parameter may need to be quoted in some manner for it to survive the shell's
3873+command-line parsing.</p>
3874+<dl>
3875+
3876+<dt><code>--db=CONFIG_FILE</code></dt><dd>
3877+<p>This tells rsyncdb what DB-config file to read for the DB setup.  This is
3878+the same as the option in rsync, so refer to that manpage for full details.</p>
3879+</dd>
3880+
3881+<dt><code>--db-lax</code></dt><dd>
3882+<p>This option works just like it does in rsync, so refer to that manpage for
3883+full details.</p>
3884+</dd>
3885+
3886+<dt><code>--no-recursive, --no-r</code></dt><dd>
3887+<p>This disables the default recursive directory scan that is performed on the
3888+listed directory args.  The options <code>--recursive</code> and <code>-r</code> are also
3889+accepted, if someone wants to override an earlier <code>--no-r</code> override.</p>
3890+</dd>
3891+
3892+<dt><code>--sums=SUMS, -s</code></dt><dd>
3893+<p>Only output/update the listed checksum types. By default we deal with just
3894+the newer md5 checksums (i.e.  <code>--sums=5</code>).</p>
3895+<p>Note that this option does NOT affect the order that checksums are output
3896+if &quot;-&#8288;o s&quot; is enabled, so <code>-s5,4</code> is the same as <code>-s4,5</code>.</p>
3897+</dd>
3898+
3899+<dt><code>--output=STR, -o</code></dt><dd>
3900+<p>The output option lets you specify one or more letters indicating what
3901+information should be output.  If <code>--output</code> is not specified, the default
3902+is either &quot;dn&quot; or (with <code>--check</code>) &quot;dni&quot;.</p>
3903+<p>The following letters are accepted in the string:</p>
3904+<ul>
3905+<li><code>d</code> outputs &quot;... dir_name ...&quot; lines for each directory in our scan.  if
3906+&quot;d&quot; is omitted, then this progress indictor is not output.</li>
3907+<li><code>n</code> includes the file's name in the per-file output. These lines are only
3908+output for changed files unless &quot;u&quot; is given.  The &quot;n&quot; option is implied
3909+by every other output option letter except &quot;d&quot;.</li>
3910+<li><code>s</code> includes the checksum info in the per-file output.</li>
3911+<li><code>c</code> is a synonym for 's'.</li>
3912+<li><code>i</code> includes itemized change info in the per-file output.
3913+<ul>
3914+<li><code>!i</code> indicates that the time and/or size is wrong.</li>
3915+<li><code>+4</code> indicates the MD4 sum is missing.</li>
3916+<li><code>+5</code> indicates the MD5 sum is missing.</li>
3917+<li><code>!4</code> indicates the MD4 sum is wrong.</li>
3918+<li><code>!5</code> indicates the MD5 sum is wrong.</li>
3919+<li><code>?4</code> indicates an unknown MD4 difference.  This can happen if we didn't
3920+need to read the file; i.e. if the time/size is wrong and no sum info
3921+was requested.</li>
3922+<li><code>?5</code> indicates an unknown MD5 difference.</li>
3923+</ul>
3924+</li>
3925+<li><code>u</code> includes unchanged files in the per-file output lines.</li>
3926+</ul>
3927+</dd>
3928+
3929+<dt><code>--check, -c</code></dt><dd>
3930+<p>Check the checksums (forcing the reading of all the files) and fix any
3931+issues that are found.  Makes <code>--output</code> default to &quot;dni&quot;.</p>
3932+</dd>
3933+
3934+<dt><code>--clean</code></dt><dd>
3935+<p>Makes a temp-DB of all the inodes that we find in all the listed
3936+directories and removes any extraneous checksums from the DB.  You will
3937+need to specify all the mounted directories that are present (and listed as
3938+mounted) in the DB on this host or else the checksums from the unvisited
3939+directories will be discarded from the DB.  If you want to just -&#8288;-&#8288;clean
3940+without adding or updating the info of new or changed files, specify
3941+<code>--no-update</code> as well.</p>
3942+</dd>
3943+
3944+<dt><code>--no-update, -N</code></dt><dd>
3945+<p>Avoids updating/adding info with <code>--check</code> and/or <code>--clean</code>.</p>
3946+</dd>
3947+
3948+<dt><code>--quiet, -q</code></dt><dd>
3949+<p>Disable the default (non-error) output settings.  This turns off the
3950+messages that <code>--init</code>, <code>--mount</code>, and <code>--clean</code> output, and makes the
3951+default for <code>--output</code> be nothing (though an explicit <code>--output</code> option is
3952+not affected).</p>
3953+</dd>
3954+
3955+<dt><code>--init</code></dt><dd>
3956+<p>Create the tables in the DB.  If it is used on an existing DB, all the
3957+existing tables are dropped and re-created.</p>
3958+</dd>
3959+</dl>
3960+<p>This option cannot be combined with the updating or reporting of checksum
3961+information, but may be combined with <code>--mounts</code>.</p>
3962+<dl>
3963+
3964+<dt><code>--mounts</code></dt><dd>
3965+<p>Populate the &quot;disk&quot; DB with the available device numbers and change any
3966+mounted/unmount information for devices.  This should be run every time a
3967+mount-change happens that may affect a directory hierarchy in the DB.
3968+Rsyncdb will not save any checksums for a device that is not listed in the
3969+&quot;disk&quot; table.</p>
3970+<p>The helper script &quot;rsyncdb-mountinfo&quot; is used as the source of the mount
3971+information on the host, which it derives from various system files and
3972+UUID directories (if available).  That script supports the use of an
3973+override file named &quot;.rsyncdb_mount_uniq&quot; in the root of the mount as one
3974+way to manually assign unique values to a shared (mountable) device's
3975+various disks.</p>
3976+<p>Some advanced users may want to maintain the disk table themselves in order
3977+to support mounting a drive in different (or multiple) locations, etc.</p>
3978+<p>Specifying the <code>--mounts</code> option cannot be combined with updating or
3979+reporting of checksum information, but may be combined with <code>--init</code>.</p>
3980+</dd>
3981+
3982+<dt><code>--help, -h</code></dt><dd>
3983+<p>Display a summary of the options.</p>
3984+</dd>
3985+</dl>
3986+<h1>SEE ALSO</h1>
3987+<p><strong>rsync</strong>(1)</p>
3988+<h1>AUTHOR</h1>
3989+<p>Rsyncdb was written by Wayne Davison.</p>
3990+<div style="float: right"><p><i>06 Aug 2020</i></p></div>
3991+</body></html>
3992