Browse Source

deprecate master/slave terminology

the underlying metaphor refers to an inhumane practice, so using it
casually is rightfully offensive to many people. it isn't even a
particularly apt metaphor, as it suggests a strict hierarchy that is
counter to mbsync's highly symmetrical mode of operation.

the far/near terminology has been chosen as the replacement, as it is a
natural fit for the push/pull terminology. on the downside, due to these
not being nouns, a few uses are a bit awkward, and several others had to
be amended to include 'side'. also, it's conceptually quite close to
remote/local, which matches the typical use case, but is maybe a bit too
suggestive of actually non-existing limitations.

the new f/n suffixes of the -C/-R/-X options clash with pre-existing
options, so direct concatenation of short options is even less practical
than before (some suffixes of -D already clashed), but doing that leads
to unreadable command lines anyway.

as with previous deprecations, all pre-existing command line and config
options keep working, but yield a warning. the state files are silently
upgraded.
1.4
Oswald Buddenhagen 4 years ago
parent
commit
c8f402e43f
  1. 2
      NEWS
  2. 8
      TODO
  3. 95
      src/config.c
  4. 1
      src/config.h
  5. 160
      src/main.c
  6. 54
      src/mbsync.1
  7. 26
      src/mbsyncrc.sample
  8. 104
      src/run-tests.pl
  9. 410
      src/sync.c
  10. 10
      src/sync.h

2
NEWS

@ -15,6 +15,8 @@ The IMAP user query can be scripted now.
Added built-in support for macOS Keychain. Added built-in support for macOS Keychain.
The use of Master/Slave terminology has been deprecated.
[1.3.0] [1.3.0]
Network timeout handling has been added. Network timeout handling has been added.

8
TODO

@ -15,7 +15,7 @@ should complain when multiple Channels match the same folders.
propagate folder deletions even when the folders are non-empty. propagate folder deletions even when the folders are non-empty.
- verify that "most" of the folders in the Channel are still there. - verify that "most" of the folders in the Channel are still there.
- refuse to delete unpropagated messages when trashing on the remote side. - refuse to delete unpropagated messages when trashing on the remote side.
- refuse to delete master if it has unpropagated messages. symmetry? - refuse to delete far side if it has unpropagated messages. symmetry?
add message expiration based on arrival date (message date would be too add message expiration based on arrival date (message date would be too
unreliable). MaxAge; probably mutually exclusive to MaxMessages. unreliable). MaxAge; probably mutually exclusive to MaxMessages.
@ -28,9 +28,9 @@ add support for event notification callbacks.
it would be also possible to report more differentiated exit codes, but it would be also possible to report more differentiated exit codes, but
that seems too limiting in the general case. that seems too limiting in the general case.
make it possible to have different mailbox names for Master and Slave in make it possible to have different mailbox names for far and near side in
Patterns. Patterns.
- use master:slave for the pattern - use far:near for the pattern
- for quoting, use more colons: the longest sequence of colons is the - for quoting, use more colons: the longest sequence of colons is the
separator separator
- this makes Groups mostly useless, as they are mostly a workaround for this - this makes Groups mostly useless, as they are mostly a workaround for this
@ -63,7 +63,7 @@ use MULTIAPPEND and FETCH with multiple messages.
create dummies describing MIME structure of messages bigger than MaxSize. create dummies describing MIME structure of messages bigger than MaxSize.
flagging the dummy would fetch the real message. possibly remove --renew. flagging the dummy would fetch the real message. possibly remove --renew.
note that all interaction needs to happen on the slave side probably. note that all interaction needs to happen on the near side probably.
don't SELECT boxes unless really needed; in particular not for appending, don't SELECT boxes unless really needed; in particular not for appending,
and in write-only mode not before changes are made. and in write-only mode not before changes are made.

95
src/config.c

@ -174,21 +174,21 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
else if (!strcasecmp( "Flags", arg )) else if (!strcasecmp( "Flags", arg ))
*cops |= OP_FLAGS; *cops |= OP_FLAGS;
else if (!strcasecmp( "PullReNew", arg )) else if (!strcasecmp( "PullReNew", arg ))
conf->ops[S] |= OP_RENEW; conf->ops[N] |= OP_RENEW;
else if (!strcasecmp( "PullNew", arg )) else if (!strcasecmp( "PullNew", arg ))
conf->ops[S] |= OP_NEW; conf->ops[N] |= OP_NEW;
else if (!strcasecmp( "PullDelete", arg )) else if (!strcasecmp( "PullDelete", arg ))
conf->ops[S] |= OP_DELETE; conf->ops[N] |= OP_DELETE;
else if (!strcasecmp( "PullFlags", arg )) else if (!strcasecmp( "PullFlags", arg ))
conf->ops[S] |= OP_FLAGS; conf->ops[N] |= OP_FLAGS;
else if (!strcasecmp( "PushReNew", arg )) else if (!strcasecmp( "PushReNew", arg ))
conf->ops[M] |= OP_RENEW; conf->ops[F] |= OP_RENEW;
else if (!strcasecmp( "PushNew", arg )) else if (!strcasecmp( "PushNew", arg ))
conf->ops[M] |= OP_NEW; conf->ops[F] |= OP_NEW;
else if (!strcasecmp( "PushDelete", arg )) else if (!strcasecmp( "PushDelete", arg ))
conf->ops[M] |= OP_DELETE; conf->ops[F] |= OP_DELETE;
else if (!strcasecmp( "PushFlags", arg )) else if (!strcasecmp( "PushFlags", arg ))
conf->ops[M] |= OP_FLAGS; conf->ops[F] |= OP_FLAGS;
else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg )) else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg ))
*cops |= XOP_PULL|XOP_PUSH; *cops |= XOP_PULL|XOP_PUSH;
else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg )) { else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg )) {
@ -197,7 +197,7 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
cfile->err = 1; cfile->err = 1;
} }
while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL ))); while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL )));
conf->ops[M] |= XOP_HAVE_TYPE; conf->ops[F] |= XOP_HAVE_TYPE;
} else if (!strcasecmp( "SyncState", cfile->cmd )) } else if (!strcasecmp( "SyncState", cfile->cmd ))
conf->sync_state = expand_strdup( cfile->val ); conf->sync_state = expand_strdup( cfile->val );
else if (!strcasecmp( "CopyArrivalDate", cfile->cmd )) else if (!strcasecmp( "CopyArrivalDate", cfile->cmd ))
@ -214,17 +214,23 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
do { do {
if (!strcasecmp( "Both", arg )) { if (!strcasecmp( "Both", arg )) {
*cops |= op; *cops |= op;
} else if (!strcasecmp( "Master", arg )) { } else if (!strcasecmp( "Far", arg )) {
conf->ops[M] |= op; conf->ops[F] |= op;
} else if (!strcasecmp( "Slave", arg )) { } else if (!strcasecmp( "Master", arg )) { // Pre-1.4 legacy
conf->ops[S] |= op; conf->ops[F] |= op;
cfile->ms_warn = 1;
} else if (!strcasecmp( "Near", arg )) {
conf->ops[N] |= op;
} else if (!strcasecmp( "Slave", arg )) { // Pre-1.4 legacy
conf->ops[N] |= op;
cfile->ms_warn = 1;
} else if (strcasecmp( "None", arg )) { } else if (strcasecmp( "None", arg )) {
error( "%s:%d: invalid %s arg '%s'\n", error( "%s:%d: invalid %s arg '%s'\n",
cfile->file, cfile->line, boxOps[i].name, arg ); cfile->file, cfile->line, boxOps[i].name, arg );
cfile->err = 1; cfile->err = 1;
} }
} while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL ))); } while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL )));
conf->ops[M] |= op * (XOP_HAVE_EXPUNGE / OP_EXPUNGE); conf->ops[F] |= op * (XOP_HAVE_EXPUNGE / OP_EXPUNGE);
return 1; return 1;
} }
} }
@ -265,25 +271,25 @@ merge_ops( int cops, int ops[] )
int aops, op; int aops, op;
uint i; uint i;
aops = ops[M] | ops[S]; aops = ops[F] | ops[N];
if (ops[M] & XOP_HAVE_TYPE) { if (ops[F] & XOP_HAVE_TYPE) {
if (aops & OP_MASK_TYPE) { if (aops & OP_MASK_TYPE) {
if (aops & cops & OP_MASK_TYPE) { if (aops & cops & OP_MASK_TYPE) {
cfl: cfl:
error( "Conflicting Sync args specified.\n" ); error( "Conflicting Sync args specified.\n" );
return 1; return 1;
} }
ops[M] |= cops & OP_MASK_TYPE; ops[F] |= cops & OP_MASK_TYPE;
ops[S] |= cops & OP_MASK_TYPE; ops[N] |= cops & OP_MASK_TYPE;
if (cops & XOP_PULL) { if (cops & XOP_PULL) {
if (ops[S] & OP_MASK_TYPE) if (ops[N] & OP_MASK_TYPE)
goto cfl; goto cfl;
ops[S] |= OP_MASK_TYPE; ops[N] |= OP_MASK_TYPE;
} }
if (cops & XOP_PUSH) { if (cops & XOP_PUSH) {
if (ops[M] & OP_MASK_TYPE) if (ops[F] & OP_MASK_TYPE)
goto cfl; goto cfl;
ops[M] |= OP_MASK_TYPE; ops[F] |= OP_MASK_TYPE;
} }
} else if (cops & (OP_MASK_TYPE|XOP_MASK_DIR)) { } else if (cops & (OP_MASK_TYPE|XOP_MASK_DIR)) {
if (!(cops & OP_MASK_TYPE)) if (!(cops & OP_MASK_TYPE))
@ -291,20 +297,20 @@ merge_ops( int cops, int ops[] )
else if (!(cops & XOP_MASK_DIR)) else if (!(cops & XOP_MASK_DIR))
cops |= XOP_PULL|XOP_PUSH; cops |= XOP_PULL|XOP_PUSH;
if (cops & XOP_PULL) if (cops & XOP_PULL)
ops[S] |= cops & OP_MASK_TYPE; ops[N] |= cops & OP_MASK_TYPE;
if (cops & XOP_PUSH) if (cops & XOP_PUSH)
ops[M] |= cops & OP_MASK_TYPE; ops[F] |= cops & OP_MASK_TYPE;
} }
} }
for (i = 0; i < as(boxOps); i++) { for (i = 0; i < as(boxOps); i++) {
op = boxOps[i].op; op = boxOps[i].op;
if (ops[M] & (op * (XOP_HAVE_EXPUNGE / OP_EXPUNGE))) { if (ops[F] & (op * (XOP_HAVE_EXPUNGE / OP_EXPUNGE))) {
if (aops & cops & op) { if (aops & cops & op) {
error( "Conflicting %s args specified.\n", boxOps[i].name ); error( "Conflicting %s args specified.\n", boxOps[i].name );
return 1; return 1;
} }
ops[M] |= cops & op; ops[F] |= cops & op;
ops[S] |= cops & op; ops[N] |= cops & op;
} }
} }
return 0; return 0;
@ -320,7 +326,7 @@ load_config( const char *where, int pseudo )
string_list_t *chanlist, **chanlistapp; string_list_t *chanlist, **chanlistapp;
char *arg, *p; char *arg, *p;
uint len, max_size; uint len, max_size;
int cops, gcops, ms, i; int cops, gcops, fn, i;
char path[_POSIX_PATH_MAX]; char path[_POSIX_PATH_MAX];
char buf[1024]; char buf[1024];
@ -343,6 +349,7 @@ load_config( const char *where, int pseudo )
cfile.bufl = sizeof(buf) - 1; cfile.bufl = sizeof(buf) - 1;
cfile.line = 0; cfile.line = 0;
cfile.err = 0; cfile.err = 0;
cfile.ms_warn = 0;
cfile.rest = NULL; cfile.rest = NULL;
gcops = 0; gcops = 0;
@ -384,11 +391,19 @@ load_config( const char *where, int pseudo )
add_string_list( &channel->patterns, arg ); add_string_list( &channel->patterns, arg );
while ((arg = get_arg( &cfile, ARG_OPTIONAL, NULL ))); while ((arg = get_arg( &cfile, ARG_OPTIONAL, NULL )));
} }
else if (!strcasecmp( "Master", cfile.cmd )) { else if (!strcasecmp( "Far", cfile.cmd )) {
ms = M; fn = F;
goto linkst; goto linkst;
} else if (!strcasecmp( "Slave", cfile.cmd )) { } else if (!strcasecmp( "Master", cfile.cmd )) { // Pre-1.4 legacy
ms = S; fn = F;
goto olinkst;
} else if (!strcasecmp( "Near", cfile.cmd )) {
fn = N;
goto linkst;
} else if (!strcasecmp( "Slave", cfile.cmd )) { // Pre-1.4 legacy
fn = N;
olinkst:
cfile.ms_warn = 1;
linkst: linkst:
if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) { if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) {
error( "%s:%d: malformed mailbox spec\n", error( "%s:%d: malformed mailbox spec\n",
@ -399,7 +414,7 @@ load_config( const char *where, int pseudo )
*p = 0; *p = 0;
for (store = stores; store; store = store->next) for (store = stores; store; store = store->next)
if (!strcmp( store->name, cfile.val + 1 )) { if (!strcmp( store->name, cfile.val + 1 )) {
channel->stores[ms] = store; channel->stores[fn] = store;
goto stpcom; goto stpcom;
} }
error( "%s:%d: unknown store '%s'\n", error( "%s:%d: unknown store '%s'\n",
@ -408,17 +423,17 @@ load_config( const char *where, int pseudo )
continue; continue;
stpcom: stpcom:
if (*++p) if (*++p)
channel->boxes[ms] = nfstrdup( p ); channel->boxes[fn] = nfstrdup( p );
} else if (!getopt_helper( &cfile, &cops, channel )) { } else if (!getopt_helper( &cfile, &cops, channel )) {
error( "%s:%d: unknown keyword '%s'\n", cfile.file, cfile.line, cfile.cmd ); error( "%s:%d: unknown keyword '%s'\n", cfile.file, cfile.line, cfile.cmd );
cfile.err = 1; cfile.err = 1;
} }
} }
if (!channel->stores[M]) { if (!channel->stores[F]) {
error( "channel '%s' refers to no master store\n", channel->name ); error( "channel '%s' refers to no far side store\n", channel->name );
cfile.err = 1; cfile.err = 1;
} else if (!channel->stores[S]) { } else if (!channel->stores[N]) {
error( "channel '%s' refers to no slave store\n", channel->name ); error( "channel '%s' refers to no near side store\n", channel->name );
cfile.err = 1; cfile.err = 1;
} else if (merge_ops( cops, channel->ops )) } else if (merge_ops( cops, channel->ops ))
cfile.err = 1; cfile.err = 1;
@ -426,7 +441,7 @@ load_config( const char *where, int pseudo )
if (max_size != UINT_MAX) { if (max_size != UINT_MAX) {
if (!max_size) if (!max_size)
max_size = UINT_MAX; max_size = UINT_MAX;
channel->stores[M]->max_size = channel->stores[S]->max_size = max_size; channel->stores[F]->max_size = channel->stores[N]->max_size = max_size;
} }
*channelapp = channel; *channelapp = channel;
channelapp = &channel->next; channelapp = &channel->next;
@ -505,6 +520,8 @@ load_config( const char *where, int pseudo )
} }
} }
fclose (cfile.fp); fclose (cfile.fp);
if (cfile.ms_warn)
warn( "Notice: Master/Slave are deprecated; use Far/Near instead.\n" );
cfile.err |= merge_ops( gcops, global_conf.ops ); cfile.err |= merge_ops( gcops, global_conf.ops );
if (!global_conf.sync_state) if (!global_conf.sync_state)
global_conf.sync_state = expand_strdup( "~/." EXE "/" ); global_conf.sync_state = expand_strdup( "~/." EXE "/" );

1
src/config.h

@ -32,6 +32,7 @@ typedef struct {
int bufl; int bufl;
int line; int line;
int err; int err;
int ms_warn;
char *cmd, *val, *rest; char *cmd, *val, *rest;
} conffile_t; } conffile_t;

160
src/main.c

@ -78,8 +78,8 @@ PACKAGE " " VERSION " - mailbox synchronizer\n"
" -d, --delete propagate message deletions\n" " -d, --delete propagate message deletions\n"
" -f, --flags propagate message flag changes\n" " -f, --flags propagate message flag changes\n"
" -N, --renew propagate previously not propagated new messages\n" " -N, --renew propagate previously not propagated new messages\n"
" -L, --pull propagate from master to slave\n" " -L, --pull propagate from far to near side\n"
" -H, --push propagate from slave to master\n" " -H, --push propagate from near to far side\n"
" -C, --create propagate creations of mailboxes\n" " -C, --create propagate creations of mailboxes\n"
" -R, --remove propagate deletions of mailboxes\n" " -R, --remove propagate deletions of mailboxes\n"
" -X, --expunge expunge deleted messages\n" " -X, --expunge expunge deleted messages\n"
@ -92,7 +92,7 @@ PACKAGE " " VERSION " - mailbox synchronizer\n"
"\nIf neither --pull nor --push are specified, both are active.\n" "\nIf neither --pull nor --push are specified, both are active.\n"
"If neither --new, --delete, --flags nor --renew are specified, all are active.\n" "If neither --new, --delete, --flags nor --renew are specified, all are active.\n"
"Direction and operation can be concatenated like --pull-new, etc.\n" "Direction and operation can be concatenated like --pull-new, etc.\n"
"--create, --remove, and --expunge can be suffixed with -master/-slave.\n" "--create, --remove, and --expunge can be suffixed with -far/-near.\n"
"See the man page for details.\n" "See the man page for details.\n"
"\nSupported mailbox formats are: IMAP4rev1, Maildir\n" "\nSupported mailbox formats are: IMAP4rev1, Maildir\n"
"\nCompile time options:\n" "\nCompile time options:\n"
@ -205,7 +205,7 @@ stats( void )
if (l > cls) if (l > cls)
buf[t][cls - 1] = '~'; buf[t][cls - 1] = '~';
} }
progress( "\r%s M: %.*s S: %.*s", buf[2], cls, buf[0], cls, buf[1] ); progress( "\r%s F: %.*s N: %.*s", buf[2], cls, buf[0], cls, buf[1] );
} }
static int static int
@ -297,18 +297,18 @@ filter_boxes( string_list_t *boxes, const char *prefix, string_list_t *patterns
static void static void
merge_actions( channel_conf_t *chan, int ops[], int have, int mask, int def ) merge_actions( channel_conf_t *chan, int ops[], int have, int mask, int def )
{ {
if (ops[M] & have) { if (ops[F] & have) {
chan->ops[M] &= ~mask; chan->ops[F] &= ~mask;
chan->ops[M] |= ops[M] & mask; chan->ops[F] |= ops[F] & mask;
chan->ops[S] &= ~mask; chan->ops[N] &= ~mask;
chan->ops[S] |= ops[S] & mask; chan->ops[N] |= ops[N] & mask;
} else if (!(chan->ops[M] & have)) { } else if (!(chan->ops[F] & have)) {
if (global_conf.ops[M] & have) { if (global_conf.ops[F] & have) {
chan->ops[M] |= global_conf.ops[M] & mask; chan->ops[F] |= global_conf.ops[F] & mask;
chan->ops[S] |= global_conf.ops[S] & mask; chan->ops[N] |= global_conf.ops[N] & mask;
} else { } else {
chan->ops[M] |= def; chan->ops[F] |= def;
chan->ops[S] |= def; chan->ops[N] |= def;
} }
} }
} }
@ -380,7 +380,7 @@ add_named_channel( chan_ent_t ***chanapp, char *channame, int ops[] )
mbox->name = nfstrndup( boxp, boxl ); mbox->name = nfstrndup( boxp, boxl );
else else
mbox->name = nfstrndup( "INBOX", 5 ); mbox->name = nfstrndup( "INBOX", 5 );
mbox->present[M] = mbox->present[S] = BOX_POSSIBLE; mbox->present[F] = mbox->present[N] = BOX_POSSIBLE;
mbox->next = NULL; mbox->next = NULL;
*mboxapp = mbox; *mboxapp = mbox;
mboxapp = &mbox->next; mboxapp = &mbox->next;
@ -431,7 +431,7 @@ main( int argc, char **argv )
channel_conf_t *chan; channel_conf_t *chan;
string_list_t *channame; string_list_t *channame;
char *config = NULL, *opt, *ochar; char *config = NULL, *opt, *ochar;
int oind, cops = 0, op, ops[2] = { 0, 0 }, pseudo = 0; int oind, cops = 0, op, ops[2] = { 0, 0 }, pseudo = 0, ms_warn = 0;
tzset(); tzset();
gethostname( Hostname, sizeof(Hostname) ); gethostname( Hostname, sizeof(Hostname) );
@ -504,22 +504,26 @@ main( int argc, char **argv )
goto badopt; goto badopt;
DFlags |= op; DFlags |= op;
} else if (!strcmp( opt, "pull" )) } else if (!strcmp( opt, "pull" ))
cops |= XOP_PULL, ops[M] |= XOP_HAVE_TYPE; cops |= XOP_PULL, ops[F] |= XOP_HAVE_TYPE;
else if (!strcmp( opt, "push" )) else if (!strcmp( opt, "push" ))
cops |= XOP_PUSH, ops[M] |= XOP_HAVE_TYPE; cops |= XOP_PUSH, ops[F] |= XOP_HAVE_TYPE;
else if (starts_with( opt, -1, "create", 6 )) { else if (starts_with( opt, -1, "create", 6 )) {
opt += 6; opt += 6;
op = OP_CREATE|XOP_HAVE_CREATE; op = OP_CREATE|XOP_HAVE_CREATE;
lcop: lcop:
if (!*opt) if (!*opt)
cops |= op; cops |= op;
else if (!strcmp( opt, "-master" )) else if (!strcmp( opt, "-far" ))
ops[M] |= op; ops[F] |= op;
else if (!strcmp( opt, "-slave" )) else if (!strcmp( opt, "-master" )) // Pre-1.4 legacy
ops[S] |= op; ops[F] |= op, ms_warn = 1;
else if (!strcmp( opt, "-near" ))
ops[N] |= op;
else if (!strcmp( opt, "-slave" )) // Pre-1.4 legacy
ops[N] |= op, ms_warn = 1;
else else
goto badopt; goto badopt;
ops[M] |= op & (XOP_HAVE_CREATE|XOP_HAVE_REMOVE|XOP_HAVE_EXPUNGE); ops[F] |= op & (XOP_HAVE_CREATE|XOP_HAVE_REMOVE|XOP_HAVE_EXPUNGE);
} else if (starts_with( opt, -1, "remove", 6 )) { } else if (starts_with( opt, -1, "remove", 6 )) {
opt += 6; opt += 6;
op = OP_REMOVE|XOP_HAVE_REMOVE; op = OP_REMOVE|XOP_HAVE_REMOVE;
@ -529,15 +533,15 @@ main( int argc, char **argv )
op = OP_EXPUNGE|XOP_HAVE_EXPUNGE; op = OP_EXPUNGE|XOP_HAVE_EXPUNGE;
goto lcop; goto lcop;
} else if (!strcmp( opt, "no-expunge" )) } else if (!strcmp( opt, "no-expunge" ))
ops[M] |= XOP_HAVE_EXPUNGE; ops[F] |= XOP_HAVE_EXPUNGE;
else if (!strcmp( opt, "no-create" )) else if (!strcmp( opt, "no-create" ))
ops[M] |= XOP_HAVE_CREATE; ops[F] |= XOP_HAVE_CREATE;
else if (!strcmp( opt, "no-remove" )) else if (!strcmp( opt, "no-remove" ))
ops[M] |= XOP_HAVE_REMOVE; ops[F] |= XOP_HAVE_REMOVE;
else if (!strcmp( opt, "full" )) else if (!strcmp( opt, "full" ))
ops[M] |= XOP_HAVE_TYPE|XOP_PULL|XOP_PUSH; ops[F] |= XOP_HAVE_TYPE|XOP_PULL|XOP_PUSH;
else if (!strcmp( opt, "noop" )) else if (!strcmp( opt, "noop" ))
ops[M] |= XOP_HAVE_TYPE; ops[F] |= XOP_HAVE_TYPE;
else if (starts_with( opt, -1, "pull", 4 )) { else if (starts_with( opt, -1, "pull", 4 )) {
op = XOP_PULL; op = XOP_PULL;
lcac: lcac:
@ -569,11 +573,11 @@ main( int argc, char **argv )
return 1; return 1;
} }
switch (op & XOP_MASK_DIR) { switch (op & XOP_MASK_DIR) {
case XOP_PULL: ops[S] |= op & OP_MASK_TYPE; break; case XOP_PULL: ops[N] |= op & OP_MASK_TYPE; break;
case XOP_PUSH: ops[M] |= op & OP_MASK_TYPE; break; case XOP_PUSH: ops[F] |= op & OP_MASK_TYPE; break;
default: cops |= op; break; default: cops |= op; break;
} }
ops[M] |= XOP_HAVE_TYPE; ops[F] |= XOP_HAVE_TYPE;
} }
continue; continue;
} }
@ -604,15 +608,19 @@ main( int argc, char **argv )
case 'C': case 'C':
op = OP_CREATE|XOP_HAVE_CREATE; op = OP_CREATE|XOP_HAVE_CREATE;
cop: cop:
if (*ochar == 'm') if (*ochar == 'f')
ops[M] |= op, ochar++; ops[F] |= op, ochar++;
else if (*ochar == 's') else if (*ochar == 'm') // Pre-1.4 legacy
ops[S] |= op, ochar++; ops[F] |= op, ms_warn = 1, ochar++;
else if (*ochar == 'n')
ops[N] |= op, ochar++;
else if (*ochar == 's') // Pre-1.4 legacy
ops[N] |= op, ms_warn = 1, ochar++;
else if (*ochar == '-') else if (*ochar == '-')
ochar++; ochar++;
else else
cops |= op; cops |= op;
ops[M] |= op & (XOP_HAVE_CREATE|XOP_HAVE_REMOVE|XOP_HAVE_EXPUNGE); ops[F] |= op & (XOP_HAVE_CREATE|XOP_HAVE_REMOVE|XOP_HAVE_EXPUNGE);
break; break;
case 'R': case 'R':
op = OP_REMOVE|XOP_HAVE_REMOVE; op = OP_REMOVE|XOP_HAVE_REMOVE;
@ -624,7 +632,7 @@ main( int argc, char **argv )
cops |= XOP_PULL|XOP_PUSH; cops |= XOP_PULL|XOP_PUSH;
FALLTHROUGH FALLTHROUGH
case '0': case '0':
ops[M] |= XOP_HAVE_TYPE; ops[F] |= XOP_HAVE_TYPE;
break; break;
case 'n': case 'n':
case 'd': case 'd':
@ -647,13 +655,13 @@ main( int argc, char **argv )
} }
if (op & OP_MASK_TYPE) if (op & OP_MASK_TYPE)
switch (op & XOP_MASK_DIR) { switch (op & XOP_MASK_DIR) {
case XOP_PULL: ops[S] |= op & OP_MASK_TYPE; break; case XOP_PULL: ops[N] |= op & OP_MASK_TYPE; break;
case XOP_PUSH: ops[M] |= op & OP_MASK_TYPE; break; case XOP_PUSH: ops[F] |= op & OP_MASK_TYPE; break;
default: cops |= op; break; default: cops |= op; break;
} }
else else
cops |= op; cops |= op;
ops[M] |= XOP_HAVE_TYPE; ops[F] |= XOP_HAVE_TYPE;
break; break;
case 'L': case 'L':
op = XOP_PULL; op = XOP_PULL;
@ -722,6 +730,8 @@ main( int argc, char **argv )
return 1; return 1;
} }
} }
if (ms_warn)
warn( "Notice: -master/-slave/m/s suffixes are deprecated; use -far/-near/f/n instead.\n" );
if (!(DFlags & (QUIET | DEBUG_ALL)) && isatty( 1 )) if (!(DFlags & (QUIET | DEBUG_ALL)) && isatty( 1 ))
DFlags |= PROGRESS; DFlags |= PROGRESS;
@ -839,17 +849,17 @@ sync_chans( main_vars_t *mvars, int ent )
int st = mvars->chan->stores[t]->driver->get_fail_state( mvars->chan->stores[t] ); int st = mvars->chan->stores[t]->driver->get_fail_state( mvars->chan->stores[t] );
if (st != FAIL_TEMP) { if (st != FAIL_TEMP) {
info( "Skipping due to %sfailed %s store %s.\n", info( "Skipping due to %sfailed %s store %s.\n",
(st == FAIL_WAIT) ? "temporarily " : "", str_ms[t], mvars->chan->stores[t]->name ); (st == FAIL_WAIT) ? "temporarily " : "", str_fn[t], mvars->chan->stores[t]->name );
mvars->skip = 1; mvars->skip = 1;
} }
} }
if (mvars->skip) if (mvars->skip)
goto next2; goto next2;
mvars->state[M] = mvars->state[S] = ST_FRESH; mvars->state[F] = mvars->state[N] = ST_FRESH;
if ((DFlags & DEBUG_DRV) || (mvars->chan->stores[M]->driver->get_caps( NULL ) & mvars->chan->stores[S]->driver->get_caps( NULL ) & DRV_VERBOSE)) if ((DFlags & DEBUG_DRV) || (mvars->chan->stores[F]->driver->get_caps( NULL ) & mvars->chan->stores[N]->driver->get_caps( NULL ) & DRV_VERBOSE))
labels[M] = "M: ", labels[S] = "S: "; labels[F] = "F: ", labels[N] = "N: ";
else else
labels[M] = labels[S] = ""; labels[F] = labels[N] = "";
for (t = 0; t < 2; t++) { for (t = 0; t < 2; t++) {
driver_t *drv = mvars->chan->stores[t]->driver; driver_t *drv = mvars->chan->stores[t]->driver;
store_t *ctx = drv->alloc_store( mvars->chan->stores[t], labels[t] ); store_t *ctx = drv->alloc_store( mvars->chan->stores[t], labels[t] );
@ -862,7 +872,7 @@ sync_chans( main_vars_t *mvars, int ent )
drv->set_bad_callback( ctx, store_bad, AUX ); drv->set_bad_callback( ctx, store_bad, AUX );
} }
for (t = 0; ; t++) { for (t = 0; ; t++) {
info( "Opening %s store %s...\n", str_ms[t], mvars->chan->stores[t]->name ); info( "Opening %s store %s...\n", str_fn[t], mvars->chan->stores[t]->name );
mvars->drv[t]->connect_store( mvars->ctx[t], store_connected, AUX ); mvars->drv[t]->connect_store( mvars->ctx[t], store_connected, AUX );
if (t || mvars->skip) if (t || mvars->skip)
break; break;
@ -872,35 +882,35 @@ sync_chans( main_vars_t *mvars, int ent )
opened: opened:
if (mvars->skip) if (mvars->skip)
goto next; goto next;
if (mvars->state[M] != ST_OPEN || mvars->state[S] != ST_OPEN) if (mvars->state[F] != ST_OPEN || mvars->state[N] != ST_OPEN)
return; return;
if (!mvars->chanptr->boxlist && mvars->chan->patterns) { if (!mvars->chanptr->boxlist && mvars->chan->patterns) {
mvars->chanptr->boxlist = 2; mvars->chanptr->boxlist = 2;
boxes[M] = filter_boxes( mvars->boxes[M], mvars->chan->boxes[M], mvars->chan->patterns ); boxes[F] = filter_boxes( mvars->boxes[F], mvars->chan->boxes[F], mvars->chan->patterns );
boxes[S] = filter_boxes( mvars->boxes[S], mvars->chan->boxes[S], mvars->chan->patterns ); boxes[N] = filter_boxes( mvars->boxes[N], mvars->chan->boxes[N], mvars->chan->patterns );
mboxapp = &mvars->chanptr->boxes; mboxapp = &mvars->chanptr->boxes;
for (mb = sb = 0; ; ) { for (mb = sb = 0; ; ) {
char *mname = boxes[M] ? boxes[M][mb] : NULL; char *mname = boxes[F] ? boxes[F][mb] : NULL;
char *sname = boxes[S] ? boxes[S][sb] : NULL; char *sname = boxes[N] ? boxes[N][sb] : NULL;
if (!mname && !sname) if (!mname && !sname)
break; break;
mbox = nfmalloc( sizeof(*mbox) ); mbox = nfmalloc( sizeof(*mbox) );
if (!(cmp = !mname - !sname) && !(cmp = cmp_box_names( &mname, &sname ))) { if (!(cmp = !mname - !sname) && !(cmp = cmp_box_names( &mname, &sname ))) {
mbox->name = mname; mbox->name = mname;
free( sname ); free( sname );
mbox->present[M] = mbox->present[S] = BOX_PRESENT; mbox->present[F] = mbox->present[N] = BOX_PRESENT;
mb++; mb++;
sb++; sb++;
} else if (cmp < 0) { } else if (cmp < 0) {
mbox->name = mname; mbox->name = mname;
mbox->present[M] = BOX_PRESENT; mbox->present[F] = BOX_PRESENT;
mbox->present[S] = (!mb && !strcmp( mbox->name, "INBOX" )) ? BOX_PRESENT : BOX_ABSENT; mbox->present[N] = (!mb && !strcmp( mbox->name, "INBOX" )) ? BOX_PRESENT : BOX_ABSENT;
mb++; mb++;
} else { } else {
mbox->name = sname; mbox->name = sname;
mbox->present[M] = (!sb && !strcmp( mbox->name, "INBOX" )) ? BOX_PRESENT : BOX_ABSENT; mbox->present[F] = (!sb && !strcmp( mbox->name, "INBOX" )) ? BOX_PRESENT : BOX_ABSENT;
mbox->present[S] = BOX_PRESENT; mbox->present[N] = BOX_PRESENT;
sb++; sb++;
} }
mbox->next = NULL; mbox->next = NULL;
@ -908,8 +918,8 @@ sync_chans( main_vars_t *mvars, int ent )
mboxapp = &mbox->next; mboxapp = &mbox->next;
boxes_total++; boxes_total++;
} }
free( boxes[M] ); free( boxes[F] );
free( boxes[S] ); free( boxes[N] );
if (!mvars->list) if (!mvars->list)
stats(); stats();
} }
@ -938,7 +948,7 @@ sync_chans( main_vars_t *mvars, int ent )
if (!mvars->skip) if (!mvars->skip)
goto syncml; goto syncml;
} else } else
printf( "%s <=> %s\n", nz( mvars->chan->boxes[M], "INBOX" ), nz( mvars->chan->boxes[S], "INBOX" ) ); printf( "%s <=> %s\n", nz( mvars->chan->boxes[F], "INBOX" ), nz( mvars->chan->boxes[N], "INBOX" ) );
} }
next: next:
@ -956,7 +966,7 @@ sync_chans( main_vars_t *mvars, int ent )
} }
} }
mvars->cben = 1; mvars->cben = 1;
if (mvars->state[M] != ST_CLOSED || mvars->state[S] != ST_CLOSED) { if (mvars->state[F] != ST_CLOSED || mvars->state[N] != ST_CLOSED) {
mvars->skip = 1; mvars->skip = 1;
return; return;
} }
@ -1067,7 +1077,7 @@ store_listed( int sts, string_list_t *boxes, void *aux )
} }
} }
if (mvars->ctx[t]->conf->map_inbox) { if (mvars->ctx[t]->conf->map_inbox) {
debug( "adding mapped inbox to %s: %s\n", str_ms[t], mvars->ctx[t]->conf->map_inbox ); debug( "adding mapped inbox to %s store: %s\n", str_fn[t], mvars->ctx[t]->conf->map_inbox );
add_string_list( &mvars->boxes[t], mvars->ctx[t]->conf->map_inbox ); add_string_list( &mvars->boxes[t], mvars->ctx[t]->conf->map_inbox );
} }
break; break;
@ -1082,19 +1092,19 @@ store_listed( int sts, string_list_t *boxes, void *aux )
static int static int
sync_listed_boxes( main_vars_t *mvars, box_ent_t *mbox ) sync_listed_boxes( main_vars_t *mvars, box_ent_t *mbox )
{ {
if (mvars->chan->boxes[M] || mvars->chan->boxes[S]) { if (mvars->chan->boxes[F] || mvars->chan->boxes[N]) {
const char *mpfx = nz( mvars->chan->boxes[M], "" ); const char *mpfx = nz( mvars->chan->boxes[F], "" );
const char *spfx = nz( mvars->chan->boxes[S], "" ); const char *spfx = nz( mvars->chan->boxes[N], "" );
if (!mvars->list) { if (!mvars->list) {
nfasprintf( &mvars->names[M], "%s%s", mpfx, mbox->name ); nfasprintf( &mvars->names[F], "%s%s", mpfx, mbox->name );
nfasprintf( &mvars->names[S], "%s%s", spfx, mbox->name ); nfasprintf( &mvars->names[N], "%s%s", spfx, mbox->name );
sync_boxes( mvars->ctx, (const char * const *)mvars->names, mbox->present, mvars->chan, done_sync_2_dyn, mvars ); sync_boxes( mvars->ctx, (const char * const *)mvars->names, mbox->present, mvars->chan, done_sync_2_dyn, mvars );
return 1; return 1;
} }
printf( "%s%s <=> %s%s\n", mpfx, mbox->name, spfx, mbox->name ); printf( "%s%s <=> %s%s\n", mpfx, mbox->name, spfx, mbox->name );
} else { } else {
if (!mvars->list) { if (!mvars->list) {
mvars->names[M] = mvars->names[S] = mbox->name; mvars->names[F] = mvars->names[N] = mbox->name;
sync_boxes( mvars->ctx, (const char * const *)mvars->names, mbox->present, mvars->chan, done_sync, mvars ); sync_boxes( mvars->ctx, (const char * const *)mvars->names, mbox->present, mvars->chan, done_sync, mvars );
return 1; return 1;
} }
@ -1108,8 +1118,8 @@ done_sync_2_dyn( int sts, void *aux )
{ {
main_vars_t *mvars = (main_vars_t *)aux; main_vars_t *mvars = (main_vars_t *)aux;
free( mvars->names[M] ); free( mvars->names[F] );
free( mvars->names[S] ); free( mvars->names[N] );
done_sync( sts, aux ); done_sync( sts, aux );
} }
@ -1123,11 +1133,11 @@ done_sync( int sts, void *aux )
stats(); stats();
if (sts) { if (sts) {
mvars->ret = 1; mvars->ret = 1;
if (sts & (SYNC_BAD(M) | SYNC_BAD(S))) { if (sts & (SYNC_BAD(F) | SYNC_BAD(N))) {
if (sts & SYNC_BAD(M)) if (sts & SYNC_BAD(F))
mvars->state[M] = ST_CLOSED; mvars->state[F] = ST_CLOSED;
if (sts & SYNC_BAD(S)) if (sts & SYNC_BAD(N))
mvars->state[S] = ST_CLOSED; mvars->state[N] = ST_CLOSED;
mvars->skip = 1; mvars->skip = 1;
} }
} }

54
src/mbsync.1

@ -57,13 +57,13 @@ line are ignored.
Don't synchronize anything, but list all mailboxes in the selected channels Don't synchronize anything, but list all mailboxes in the selected channels
and exit. and exit.
.TP .TP
\fB-C\fR[\fBm\fR][\fBs\fR], \fB--create\fR[\fB-master\fR|\fB-slave\fR] \fB-C\fR[\fBf\fR][\fBn\fR], \fB--create\fR[\fB-far\fR|\fB-near\fR]
Override any \fBCreate\fR options from the config file. See below. Override any \fBCreate\fR options from the config file. See below.
.TP .TP
\fB-R\fR[\fBm\fR][\fBs\fR], \fB--remove\fR[\fB-master\fR|\fB-slave\fR] \fB-R\fR[\fBf\fR][\fBn\fR], \fB--remove\fR[\fB-far\fR|\fB-near\fR]
Override any \fBRemove\fR options from the config file. See below. Override any \fBRemove\fR options from the config file. See below.
.TP .TP
\fB-X\fR[\fBm\fR][\fBs\fR], \fB--expunge\fR[\fB-master\fR|\fB-slave\fR] \fB-X\fR[\fBf\fR][\fBn\fR], \fB--expunge\fR[\fB-far\fR|\fB-near\fR]
Override any \fBExpunge\fR options from the config file. See below. Override any \fBExpunge\fR options from the config file. See below.
.TP .TP
{\fB-n\fR|\fB-N\fR|\fB-d\fR|\fB-f\fR|\fB-0\fR|\fB-F\fR},\ {\fB-n\fR|\fB-N\fR|\fB-d\fR|\fB-f\fR|\fB-0\fR|\fB-F\fR},\
@ -174,7 +174,7 @@ If \fIsize\fR is 0, the maximum message size is \fBunlimited\fR.
\fBMapInbox\fR \fImailbox\fR \fBMapInbox\fR \fImailbox\fR
Create a virtual mailbox (relative to \fBPath\fR) which aliases Create a virtual mailbox (relative to \fBPath\fR) which aliases
the \fBINBOX\fR. Makes sense in conjunction with \fBPatterns\fR in the the \fBINBOX\fR. Makes sense in conjunction with \fBPatterns\fR in the
Channels section, though with a Maildir slave, you probably want to Channels section, though with a Maildir near side, you probably want to
place \fBInbox\fR under \fBPath\fR instead. place \fBInbox\fR under \fBPath\fR instead.
This virtual mailbox does not support subfolders. This virtual mailbox does not support subfolders.
. .
@ -511,8 +511,8 @@ This option make sense only in conjunction with \fBPatterns\fR.
Define the Channel \fIname\fR, opening a section for its parameters. Define the Channel \fIname\fR, opening a section for its parameters.
. .
.TP .TP
{\fBMaster\fR|\fBSlave\fR} \fB:\fIstore\fB:\fR[\fImailbox\fR] {\fBFar\fR|\fBNear\fR} \fB:\fIstore\fB:\fR[\fImailbox\fR]
Specify the Master resp. Slave Store to be connected by this Channel. Specify the far resp. near side Store to be connected by this Channel.
If \fBPatterns\fR are specified, \fImailbox\fR is interpreted as a If \fBPatterns\fR are specified, \fImailbox\fR is interpreted as a
prefix which is not matched against the patterns, and which is not prefix which is not matched against the patterns, and which is not
affected by mailbox list overrides. affected by mailbox list overrides.
@ -521,8 +521,8 @@ Otherwise, if \fImailbox\fR is omitted, \fBINBOX\fR is assumed.
.TP .TP
\fBPattern\fR[\fBs\fR] [\fB!\fR]\fIpattern\fR ... \fBPattern\fR[\fBs\fR] [\fB!\fR]\fIpattern\fR ...
Instead of synchronizing only one mailbox pair, synchronize all mailboxes Instead of synchronizing only one mailbox pair, synchronize all mailboxes
that match the \fIpattern\fR(s). The mailbox names are the same on both that match the \fIpattern\fR(s). The mailbox names are the same on the far
Master and Slave. Patterns are IMAP4 patterns, i.e., \fB*\fR matches anything and near side. Patterns are IMAP4 patterns, i.e., \fB*\fR matches anything
and \fB%\fR matches anything up to the next hierarchy delimiter. Prepending and \fB%\fR matches anything up to the next hierarchy delimiter. Prepending
\fB!\fR to a pattern makes it an exclusion. Multiple patterns can be specified \fB!\fR to a pattern makes it an exclusion. Multiple patterns can be specified
(either by supplying multiple arguments or by using \fBPattern\fR multiple (either by supplying multiple arguments or by using \fBPattern\fR multiple
@ -539,12 +539,12 @@ Example: "\fBPatterns\fR\ \fI%\ !Trash\fR"
.TP .TP
\fBMaxSize\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR] \fBMaxSize\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR]
Analogous to the homonymous option in the Stores section, but applies equally Analogous to the homonymous option in the Stores section, but applies equally
to Master and Slave. Note that this actually modifies the Stores, so take care to Far and Near. Note that this actually modifies the Stores, so take care
not to provide conflicting settings if you use the Stores in multiple Channels. not to provide conflicting settings if you use the Stores in multiple Channels.
. .
.TP .TP
\fBMaxMessages\fR \fIcount\fR \fBMaxMessages\fR \fIcount\fR
Sets the maximum number of messages to keep in each Slave mailbox. Sets the maximum number of messages to keep in each near side mailbox.
This is useful for mailboxes where you keep a complete archive on the server, This is useful for mailboxes where you keep a complete archive on the server,
but want to mirror only the last messages (for instance, for mailing lists). but want to mirror only the last messages (for instance, for mailing lists).
The messages that were the first to arrive in the mailbox (independently of The messages that were the first to arrive in the mailbox (independently of
@ -568,9 +568,9 @@ case you need to enable this option.
\fBSync\fR {\fBNone\fR|[\fBPull\fR] [\fBPush\fR] [\fBNew\fR] [\fBReNew\fR] [\fBDelete\fR] [\fBFlags\fR]|\fBAll\fR} \fBSync\fR {\fBNone\fR|[\fBPull\fR] [\fBPush\fR] [\fBNew\fR] [\fBReNew\fR] [\fBDelete\fR] [\fBFlags\fR]|\fBAll\fR}
Select the synchronization operation(s) to perform: Select the synchronization operation(s) to perform:
.br .br
\fBPull\fR - propagate changes from Master to Slave. \fBPull\fR - propagate changes from far to near side.
.br .br
\fBPush\fR - propagate changes from Slave to Master. \fBPush\fR - propagate changes from near to far side.
.br .br
\fBNew\fR - propagate newly appeared messages. \fBNew\fR - propagate newly appeared messages.
.br .br
@ -602,10 +602,10 @@ In the first style, the flags select entire rows/colums in the matrix. Only
the cells which are selected both horizontally and vertically are asserted. the cells which are selected both horizontally and vertically are asserted.
Specifying no flags from a class is like specifying all flags from this class. Specifying no flags from a class is like specifying all flags from this class.
For example, "\fBSync\fR\ \fBPull\fR\ \fBNew\fR\ \fBFlags\fR" will propagate For example, "\fBSync\fR\ \fBPull\fR\ \fBNew\fR\ \fBFlags\fR" will propagate
new messages and flag changes from the Master to the Slave, new messages and flag changes from the far side to the near side,
"\fBSync\fR\ \fBNew\fR\ \fBDelete\fR" will propagate message arrivals and "\fBSync\fR\ \fBNew\fR\ \fBDelete\fR" will propagate message arrivals and
deletions both ways, and "\fBSync\fR\ \fBPush\fR" will propagate all changes deletions both ways, and "\fBSync\fR\ \fBPush\fR" will propagate all changes
from the Slave to the Master. from the near side to the far side.
.br .br
In the second style, direction flags are concatenated with type flags; every In the second style, direction flags are concatenated with type flags; every
compound flag immediately asserts a cell in the matrix. In addition to at least compound flag immediately asserts a cell in the matrix. In addition to at least
@ -613,22 +613,22 @@ one compound flag, the individual flags can be used as well, but as opposed to
the first style, they immediately assert all cells in their respective the first style, they immediately assert all cells in their respective
row/column. For example, row/column. For example,
"\fBSync\fR\ \fBPullNew\fR\ \fBPullDelete\fR\ \fBPush\fR" will propagate "\fBSync\fR\ \fBPullNew\fR\ \fBPullDelete\fR\ \fBPush\fR" will propagate
message arrivals and deletions from the Master to the Slave and any changes message arrivals and deletions from the far side to the near side and any
from the Slave to the Master. changes from the near side to the far side.
Note that it is not allowed to assert a cell in two ways, e.g. Note that it is not allowed to assert a cell in two ways, e.g.
"\fBSync\fR\ \fBPullNew\fR\ \fBPull\fR" and "\fBSync\fR\ \fBPullNew\fR\ \fBPull\fR" and
"\fBSync\fR\ \fBPullNew\fR\ \fBDelete\fR\ \fBPush\fR" induce error messages. "\fBSync\fR\ \fBPullNew\fR\ \fBDelete\fR\ \fBPush\fR" induce error messages.
. .
.TP .TP
\fBCreate\fR {\fBNone\fR|\fBMaster\fR|\fBSlave\fR|\fBBoth\fR} \fBCreate\fR {\fBNone\fR|\fBFar\fR|\fBNear\fR|\fBBoth\fR}
Automatically create missing mailboxes [on the Master/Slave]. Automatically create missing mailboxes [on the far/near side].
Otherwise print an error message and skip that mailbox pair if a mailbox Otherwise print an error message and skip that mailbox pair if a mailbox
and the corresponding sync state does not exist. and the corresponding sync state does not exist.
(Global default: \fBNone\fR) (Global default: \fBNone\fR)
. .
.TP .TP
\fBRemove\fR {\fBNone\fR|\fBMaster\fR|\fBSlave\fR|\fBBoth\fR} \fBRemove\fR {\fBNone\fR|\fBFar\fR|\fBNear\fR|\fBBoth\fR}
Propagate mailbox deletions [to the Master/Slave]. Propagate mailbox deletions [to the far/near side].
Otherwise print an error message and skip that mailbox pair if a mailbox Otherwise print an error message and skip that mailbox pair if a mailbox
does not exist but the corresponding sync state does. does not exist but the corresponding sync state does.
.br .br
@ -640,8 +640,8 @@ Note that for safety, non-empty mailboxes are never deleted.
(Global default: \fBNone\fR) (Global default: \fBNone\fR)
. .
.TP .TP
\fBExpunge\fR {\fBNone\fR|\fBMaster\fR|\fBSlave\fR|\fBBoth\fR} \fBExpunge\fR {\fBNone\fR|\fBFar\fR|\fBNear\fR|\fBBoth\fR}
Permanently remove all messages [on the Master/Slave] marked for deletion. Permanently remove all messages [on the far/near side] marked for deletion.
See \fBRECOMMENDATIONS\fR below. See \fBRECOMMENDATIONS\fR below.
(Global default: \fBNone\fR) (Global default: \fBNone\fR)
. .
@ -666,15 +666,15 @@ which in turn are overridden by command line switches.
\fBSyncState\fR {\fB*\fR|\fIpath\fR} \fBSyncState\fR {\fB*\fR|\fIpath\fR}
Set the location of this Channel's synchronization state files. Set the location of this Channel's synchronization state files.
\fB*\fR means that the state should be saved in a file named .mbsyncstate \fB*\fR means that the state should be saved in a file named .mbsyncstate
in the Slave mailbox itself; this has the advantage that you do not need in the near side mailbox itself; this has the advantage that you do not need
to handle the state file separately if you delete the mailbox, but it works to handle the state file separately if you delete the mailbox, but it works
only with Maildir mailboxes, obviously. only with Maildir mailboxes, obviously.
Otherwise this is interpreted as a string to prepend to the Slave mailbox Otherwise this is interpreted as a string to prepend to the near side mailbox
name to make up a complete path. name to make up a complete path.
.br .br
This option can be used outside any section for a global effect. In this case This option can be used outside any section for a global effect. In this case
the appended string is made up according to the pattern the appended string is made up according to the pattern
\fB:\fImaster\fB:\fImaster-box\fB_:\fIslave\fB:\fIslave-box\fR \fB:\fIfar-store\fB:\fIfar-box\fB_:\fInear-store\fB:\fInear-box\fR
(see also \fBFieldDelimiter\fR below). (see also \fBFieldDelimiter\fR below).
.br .br
(Global default: \fI~/.mbsync/\fR). (Global default: \fI~/.mbsync/\fR).
@ -734,11 +734,11 @@ If \fBmbsync\fR's output is connected to a console, it will print progress
counters by default. The output will look like this: counters by default. The output will look like this:
.P .P
.in +4 .in +4
C: 1/2 B: 3/4 M: +13/13 *23/42 #0/0 S: +0/7 *0/0 #0/0 C: 1/2 B: 3/4 F: +13/13 *23/42 #0/0 N: +0/7 *0/0 #0/0
.in -4 .in -4
.P .P
This represents the cumulative progress over channels, boxes, and messages This represents the cumulative progress over channels, boxes, and messages
affected on master and slave, respectively. affected on the far and near side, respectively.
The message counts represent added messages, messages with updated flags, The message counts represent added messages, messages with updated flags,
and trashed messages, respectively. and trashed messages, respectively.
No attempt is made to calculate the totals in advance, so they grow over No attempt is made to calculate the totals in advance, so they grow over

26
src/mbsyncrc.sample

@ -23,9 +23,9 @@ Pass xxxxxxxx
#PassCmd "echo -ne 'GET myIsp\\tpassword' | pwmc datafile" #PassCmd "echo -ne 'GET myIsp\\tpassword' | pwmc datafile"
Channel work Channel work
Master :work: Far :work:
Slave :local:work Near :local:work
Expunge Slave Expunge Near
Sync PullNew Push Sync PullNew Push
@ -35,8 +35,8 @@ Port 6789
RequireSSL no RequireSSL no
Channel personal Channel personal
Master :personal: Far :personal:
Slave :local:personal Near :local:personal
Expunge Both Expunge Both
MaxMessages 150 MaxMessages 150
MaxSize 200k MaxSize 200k
@ -45,8 +45,8 @@ IMAPStore remote
Tunnel "ssh -q host.remote.com /usr/sbin/imapd" Tunnel "ssh -q host.remote.com /usr/sbin/imapd"
Channel remote Channel remote
Master :remote: Far :remote:
Slave :local:remote Near :local:remote
Group boxes Group boxes
@ -65,8 +65,8 @@ RequireSSL no
UseTLSv1 no UseTLSv1 no
Channel rst Channel rst
Master :st1:somebox Far :st1:somebox
Slave :st2: Near :st2:
IMAPAccount server IMAPAccount server
@ -84,14 +84,14 @@ Path ~/Maildir/
SubFolders Verbatim SubFolders Verbatim
Channel o2o Channel o2o
Master :server: Far :server:
Slave :mirror: Near :mirror:
Patterns % Patterns %
Group partial o2o:inbox,sent-mail,foobar Group partial o2o:inbox,sent-mail,foobar
# INBOX => server, INBOX.foo => server.foo, etc. # INBOX => server, INBOX.foo => server.foo, etc.
Channel inbox Channel inbox
Master :server:INBOX Far :server:INBOX
Slave :mirror:server Near :mirror:server
Patterns * Patterns *

104
src/run-tests.pl

@ -37,9 +37,9 @@ sub test($$$@);
################################################################################ ################################################################################
# Format of the test defs: [ master, slave, state ] # Format of the test defs: [ far, near, state ]
# master/slave: [ maxuid, { seq, uid, flags }... ] # far/near: [ maxuid, { seq, uid, flags }... ]
# state: [ MaxPulledUid, MaxExpiredMasterUid, MaxPushedUid, { muid, suid, flags }... ] # state: [ MaxPulledUid, MaxExpiredFarUid, MaxPushedUid, { muid, suid, flags }... ]
use enum qw(:=1 A..Z); use enum qw(:=1 A..Z);
sub mn($) { chr(64 + shift) } sub mn($) { chr(64 + shift) }
@ -78,7 +78,7 @@ my @X02 = (
); );
test("full + expunge both", \@x01, \@X02, @O02); test("full + expunge both", \@x01, \@X02, @O02);
my @O03 = ("", "", "Expunge Slave\n"); my @O03 = ("", "", "Expunge Near\n");
#show("01", "03", "03"); #show("01", "03", "03");
my @X03 = ( my @X03 = (
[ 10, [ 10,
@ -88,7 +88,7 @@ my @X03 = (
[ 9, 0, 9, [ 9, 0, 9,
1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 0, "T", 6, 0, "", 7, 0, "T", 10, 9, "", 9, 10, "" ], 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 0, "T", 6, 0, "", 7, 0, "T", 10, 9, "", 9, 10, "" ],
); );
test("full + expunge slave", \@x01, \@X03, @O03); test("full + expunge near side", \@x01, \@X03, @O03);
my @O04 = ("", "", "Sync Pull\n"); my @O04 = ("", "", "Sync Pull\n");
#show("01", "04", "04"); #show("01", "04", "04");
@ -183,7 +183,7 @@ my @X22 = (
[ 2, 0, 1, [ 2, 0, 1,
3, 1, "", 1, 2, "", 2, 0, "^" ], 3, 1, "", 1, 2, "", 2, 0, "^" ],
); );
test("slave max size", \@X11, \@X22, @O22); test("near side max size", \@X11, \@X22, @O22);
# expiration tests # expiration tests
@ -258,7 +258,7 @@ sub qm($)
return $_; return $_;
} }
# $master, $slave, $channel # $far, $near, $channel
sub writecfg($$$) sub writecfg($$$)
{ {
open(FILE, ">", ".mbsyncrc") or open(FILE, ">", ".mbsyncrc") or
@ -266,17 +266,17 @@ sub writecfg($$$)
print FILE print FILE
"FSync no "FSync no
MaildirStore master MaildirStore far
Path ./ Path ./
Inbox ./master Inbox ./far
".shift()." ".shift()."
MaildirStore slave MaildirStore near
Path ./ Path ./
Inbox ./slave Inbox ./near
".shift()." ".shift()."
Channel test Channel test
Master :master: Far :far:
Slave :slave: Near :near:
SyncState * SyncState *
".shift(); ".shift();
close FILE; close FILE;
@ -371,8 +371,8 @@ sub showbox($)
# $filename # $filename
# Output: # Output:
# [ maxuid[M], mmaxxuid, maxuid[S], # [ maxuid[F], maxxfuid, maxuid[N],
# uid[M], uid[S], "flags", ... ], # uid[F], uid[N], "flags", ... ],
sub showstate($) sub showstate($)
{ {
my ($fn) = @_; my ($fn) = @_;
@ -400,7 +400,7 @@ sub showstate($)
return; return;
} }
my @T = ($hdr{'MaxPulledUid'} // "missing", my @T = ($hdr{'MaxPulledUid'} // "missing",
$hdr{'MaxExpiredMasterUid'} // "0", $hdr{'MaxExpiredFarUid'} // "0",
$hdr{'MaxPushedUid'} // "missing"); $hdr{'MaxPushedUid'} // "missing");
for (@ls) { for (@ls) {
/^(\d+) (\d+) (.*)$/; /^(\d+) (\d+) (.*)$/;
@ -414,8 +414,8 @@ sub showchan($)
{ {
my ($fn) = @_; my ($fn) = @_;
showbox("master"); showbox("far");
showbox("slave"); showbox("near");
showstate($fn); showstate($fn);
} }
@ -428,17 +428,17 @@ sub show($$$)
eval "\@sfx = \@O$sfxn"; eval "\@sfx = \@O$sfxn";
mkchan($sp[0], $sp[1], @{ $sp[2] }); mkchan($sp[0], $sp[1], @{ $sp[2] });
print "my \@x$sx = (\n"; print "my \@x$sx = (\n";
showchan("slave/.mbsyncstate"); showchan("near/.mbsyncstate");
print ");\n"; print ");\n";
&writecfg(@sfx); &writecfg(@sfx);
runsync("", ""); runsync("", "");
killcfg(); killcfg();
print "my \@X$tx = (\n"; print "my \@X$tx = (\n";
showchan("slave/.mbsyncstate"); showchan("near/.mbsyncstate");
print ");\n"; print ");\n";
print "test(\"\", \\\@x$sx, \\\@X$tx, \@O$sfxn);\n\n"; print "test(\"\", \\\@x$sx, \\\@X$tx, \@O$sfxn);\n\n";
rmtree "slave"; rmtree "near";
rmtree "master"; rmtree "far";
} }
# $boxname, $maxuid, @msgs # $boxname, $maxuid, @msgs
@ -462,16 +462,16 @@ sub mkbox($$@)
} }
} }
# \@master, \@slave, @syncstate # \@far, \@near, @syncstate
sub mkchan($$@) sub mkchan($$@)
{ {
my ($m, $s, @t) = @_; my ($m, $s, @t) = @_;
&mkbox("master", @{ $m }); &mkbox("far", @{ $m });
&mkbox("slave", @{ $s }); &mkbox("near", @{ $s });
open(FILE, ">", "slave/.mbsyncstate") or open(FILE, ">", "near/.mbsyncstate") or
die "Cannot create sync state.\n"; die "Cannot create sync state.\n";
print FILE "MasterUidValidity 1\nMaxPulledUid ".shift(@t)."\n". print FILE "FarUidValidity 1\nMaxPulledUid ".shift(@t)."\n".
"SlaveUidValidity 1\nMaxExpiredMasterUid ".shift(@t)."\nMaxPushedUid ".shift(@t)."\n\n"; "NearUidValidity 1\nMaxExpiredFarUid ".shift(@t)."\nMaxPushedUid ".shift(@t)."\n\n";
while (@t) { while (@t) {
print FILE shift(@t)." ".shift(@t)." ".shift(@t)."\n"; print FILE shift(@t)." ".shift(@t)." ".shift(@t)."\n";
} }
@ -514,13 +514,13 @@ sub ckbox($$@)
# $filename, @syncstate # $filename, @syncstate
sub ckstate($@) sub ckstate($@)
{ {
my ($fn, $mmaxuid, $mmaxxuid, $smaxuid, @T) = @_; my ($fn, $fmaxuid, $maxxfuid, $nmaxuid, @T) = @_;
my %hdr; my %hdr;
$hdr{'MasterUidValidity'} = "1"; $hdr{'FarUidValidity'} = "1";
$hdr{'SlaveUidValidity'} = "1"; $hdr{'NearUidValidity'} = "1";
$hdr{'MaxPulledUid'} = $mmaxuid; $hdr{'MaxPulledUid'} = $fmaxuid;
$hdr{'MaxPushedUid'} = $smaxuid; $hdr{'MaxPushedUid'} = $nmaxuid;
$hdr{'MaxExpiredMasterUid'} = $mmaxxuid if ($mmaxxuid ne 0); $hdr{'MaxExpiredFarUid'} = $maxxfuid if ($maxxfuid ne 0);
open(FILE, "<", $fn) or die "Cannot read sync state $fn.\n"; open(FILE, "<", $fn) or die "Cannot read sync state $fn.\n";
chomp(my @ls = <FILE>); chomp(my @ls = <FILE>);
close FILE; close FILE;
@ -573,8 +573,8 @@ sub ckchan($$)
{ {
my ($F, $cs) = @_; my ($F, $cs) = @_;
my $rslt = ckstate($F, @{ $$cs[2] }); my $rslt = ckstate($F, @{ $$cs[2] });
$rslt |= &ckbox("master", @{ $$cs[0] }); $rslt |= &ckbox("far", @{ $$cs[0] });
$rslt |= &ckbox("slave", @{ $$cs[1] }); $rslt |= &ckbox("near", @{ $$cs[1] });
return $rslt; return $rslt;
} }
@ -618,8 +618,8 @@ sub printchan($)
{ {
my ($cs) = @_; my ($cs) = @_;
&printbox("master", @{ $$cs[0] }); &printbox("far", @{ $$cs[0] });
&printbox("slave", @{ $$cs[1] }); &printbox("near", @{ $$cs[1] });
printstate(@{ $$cs[2] }); printstate(@{ $$cs[2] });
} }
@ -645,7 +645,7 @@ sub test($$$@)
mkchan($$sx[0], $$sx[1], @{ $$sx[2] }); mkchan($$sx[0], $$sx[1], @{ $$sx[2] });
my ($xc, @ret) = runsync("-J", "1-initial.log"); my ($xc, @ret) = runsync("-J", "1-initial.log");
if ($xc || ckchan("slave/.mbsyncstate.new", $tx)) { if ($xc || ckchan("near/.mbsyncstate.new", $tx)) {
print "Input:\n"; print "Input:\n";
printchan($sx); printchan($sx);
print "Options:\n"; print "Options:\n";
@ -654,16 +654,16 @@ sub test($$$@)
print "Expected result:\n"; print "Expected result:\n";
printchan($tx); printchan($tx);
print "Actual result:\n"; print "Actual result:\n";
showchan("slave/.mbsyncstate.new"); showchan("near/.mbsyncstate.new");
} }
print "Debug output:\n"; print "Debug output:\n";
print @ret; print @ret;
exit 1; exit 1;
} }
my @nj = readfile("slave/.mbsyncstate.journal"); my @nj = readfile("near/.mbsyncstate.journal");
my ($jxc, @jret) = runsync("-0 --no-expunge", "2-replay.log"); my ($jxc, @jret) = runsync("-0 --no-expunge", "2-replay.log");
if ($jxc || ckstate("slave/.mbsyncstate", @{ $$tx[2] })) { if ($jxc || ckstate("near/.mbsyncstate", @{ $$tx[2] })) {
print "Journal replay failed.\n"; print "Journal replay failed.\n";
print "Options:\n"; print "Options:\n";
print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ], [ \"-0\", \"--no-expunge\" ]\n"; print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ], [ \"-0\", \"--no-expunge\" ]\n";
@ -674,7 +674,7 @@ sub test($$$@)
print "Expected New State:\n"; print "Expected New State:\n";
printstate(@{ $$tx[2] }); printstate(@{ $$tx[2] });
print "New State:\n"; print "New State:\n";
showstate("slave/.mbsyncstate"); showstate("near/.mbsyncstate");
} }
print "Debug output:\n"; print "Debug output:\n";
print @jret; print @jret;
@ -682,7 +682,7 @@ sub test($$$@)
} }
my ($ixc, @iret) = runsync("", "3-verify.log"); my ($ixc, @iret) = runsync("", "3-verify.log");
if ($ixc || ckchan("slave/.mbsyncstate", $tx)) { if ($ixc || ckchan("near/.mbsyncstate", $tx)) {
print "Idempotence verification run failed.\n"; print "Idempotence verification run failed.\n";
print "Input == Expected result:\n"; print "Input == Expected result:\n";
printchan($tx); printchan($tx);
@ -690,15 +690,15 @@ sub test($$$@)
print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ]\n"; print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ]\n";
if (!$ixc) { if (!$ixc) {
print "Actual result:\n"; print "Actual result:\n";
showchan("slave/.mbsyncstate"); showchan("near/.mbsyncstate");
} }
print "Debug output:\n"; print "Debug output:\n";
print @iret; print @iret;
exit 1; exit 1;
} }
rmtree "slave"; rmtree "near";
rmtree "master"; rmtree "far";
my $njl = (@nj - 1) * 2; my $njl = (@nj - 1) * 2;
for (my $l = 2; $l < $njl; $l++) { for (my $l = 2; $l < $njl; $l++) {
@ -713,28 +713,28 @@ sub test($$$@)
} }
($nxc, @nret) = runsync("-J", "5-resume.log"); ($nxc, @nret) = runsync("-J", "5-resume.log");
if ($nxc || ckchan("slave/.mbsyncstate.new", $tx)) { if ($nxc || ckchan("near/.mbsyncstate.new", $tx)) {
print "Resuming from step $l/$njl failed.\n"; print "Resuming from step $l/$njl failed.\n";
print "Input:\n"; print "Input:\n";
printchan($sx); printchan($sx);
print "Options:\n"; print "Options:\n";
print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ]\n"; print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ]\n";
my @nnj = readfile("slave/.mbsyncstate.journal"); my @nnj = readfile("near/.mbsyncstate.journal");
print "Journal:\n".join("", @nnj[0..($l / 2 - 1)])."-------\n".join("", @nnj[($l / 2)..$#nnj])."\n"; print "Journal:\n".join("", @nnj[0..($l / 2 - 1)])."-------\n".join("", @nnj[($l / 2)..$#nnj])."\n";
print "Full journal:\n".join("", @nj)."\n"; print "Full journal:\n".join("", @nj)."\n";
if (!$nxc) { if (!$nxc) {
print "Expected result:\n"; print "Expected result:\n";
printchan($tx); printchan($tx);
print "Actual result:\n"; print "Actual result:\n";
showchan("slave/.mbsyncstate.new"); showchan("near/.mbsyncstate.new");
} }
print "Debug output:\n"; print "Debug output:\n";
print @nret; print @nret;
exit 1; exit 1;
} }
rmtree "slave"; rmtree "near";
rmtree "master"; rmtree "far";
} }
killcfg(); killcfg();

410
src/sync.c

File diff suppressed because it is too large Load Diff

10
src/sync.h

@ -25,8 +25,8 @@
#include "driver.h" #include "driver.h"
#define M 0 /* master */ #define F 0 // far side
#define S 1 /* slave */ #define N 1 // near side
#define OP_NEW (1<<0) #define OP_NEW (1<<0)
#define OP_RENEW (1<<1) #define OP_RENEW (1<<1)
@ -52,7 +52,7 @@ typedef struct channel_conf {
char *sync_state; char *sync_state;
string_list_t *patterns; string_list_t *patterns;
int ops[2]; int ops[2];
int max_messages; /* for slave only */ int max_messages; // For near side only.
signed char expire_unread; signed char expire_unread;
char use_internal_date; char use_internal_date;
} channel_conf_t; } channel_conf_t;
@ -67,11 +67,11 @@ extern channel_conf_t global_conf;
extern channel_conf_t *channels; extern channel_conf_t *channels;
extern group_conf_t *groups; extern group_conf_t *groups;
extern const char *str_ms[2], *str_hl[2]; extern const char *str_fn[2], *str_hl[2];
#define SYNC_OK 0 /* assumed to be 0 */ #define SYNC_OK 0 /* assumed to be 0 */
#define SYNC_FAIL 1 #define SYNC_FAIL 1
#define SYNC_BAD(ms) (4<<(ms)) #define SYNC_BAD(fn) (4<<(fn))
#define SYNC_NOGOOD 16 /* internal */ #define SYNC_NOGOOD 16 /* internal */
#define SYNC_CANCELED 32 /* internal */ #define SYNC_CANCELED 32 /* internal */

Loading…
Cancel
Save