File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / rsync / patches / db.diff
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Mar 17 00:32:36 2021 UTC (3 years, 6 months ago) by misho
Branches: rsync, MAIN
CVS tags: v3_2_3, HEAD
rsync 3.2.3

    1: Added some DB-access routines to help rsync keep extra filesystem info
    2: about the files it is dealing with.  This adds both the --db=CONFIG_FILE
    3: option and the "db config" daemon parameter.
    4: 
    5: Future 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: 
   17: To 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: 
   24: based-on: e94bad1c156fc3910f24e2b3b71a81b0b0bdeb70
   25: diff --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
   36: diff --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:  
  126: diff --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:
  155: diff --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);
  179: diff --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)
  219: diff --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.])
  279: diff --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
  298: diff --git a/db.c b/db.c
  299: new 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: +}
 2246: diff --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:  
 2320: diff --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:  
 2365: diff --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);
 2420: diff --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:  
 2467: diff --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;
 2714: diff --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
 2758: diff --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;
 2810: diff --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
 2823: diff --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"
 2843: diff --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)
 2873: diff --git a/rsyncdb-mountinfo b/rsyncdb-mountinfo
 2874: new 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";
 2960: diff --git a/rsyncdb.1.md b/rsyncdb.1.md
 2961: new 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.
 3182: diff --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;
 3217: diff -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:  
 3266: diff -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:  
 3500: diff -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
 3513: diff -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
 3526: diff -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.
 3760: diff -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>

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>