Annotation of embedaddon/rsync/patches/db.diff, revision 1.1

1.1     ! misho       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>