commit d5a5da94755877fe055b0b5d540195860ef5814d
parent 01329bdf82e932acfb5b228851177123509850b7
Author: Oswald Buddenhagen <ossi@users.sf.net>
Date: Wed, 4 May 2022 13:52:44 +0200
factor out main_sync.c & main_p.h from main.c
this moves the channel iteration & synchronization code from main(),
with all its dependencies.
then it is "re-threaded" to be more directly driven by the driver
callbacks (like sync_boxes() is), rather than being a weird state
machine.
while the code is moved, localize many variables, and use an enum
instead of #defines for the states.
Diffstat:
6 files changed, 757 insertions(+), 676 deletions(-)
diff --git a/src/Makefile.am b/src/Makefile.am
@@ -8,11 +8,12 @@ mbsync_SOURCES = \
drv_imap.c \
drv_maildir.c \
sync.c sync_state.c \
- main.c
+ main.c main_sync.c
noinst_HEADERS = \
common.h config.h socket.h \
driver.h \
- sync.h sync_p.h
+ sync.h sync_p.h \
+ main_p.h
mbsync_LDADD = $(DB_LIBS) $(SSL_LIBS) $(SOCK_LIBS) $(SASL_LIBS) $(Z_LIBS) $(KEYCHAIN_LIBS)
drv_proxy.$(OBJEXT): drv_proxy.inc
diff --git a/src/driver.c b/src/driver.c
@@ -9,6 +9,13 @@
driver_t *drivers[N_DRIVERS] = { &maildir_driver, &imap_driver };
+void
+cleanup_drivers( void )
+{
+ for (int t = 0; t < N_DRIVERS; t++)
+ drivers[t]->cleanup();
+}
+
// Keep the MESSAGE_FLAGS in sync (grep that)!
const char MsgFlags[] = { 'D', 'F', 'P', 'R', 'S', 'T' };
diff --git a/src/driver.h b/src/driver.h
@@ -297,4 +297,6 @@ store_t *proxy_alloc_store( store_t *real_ctx, const char *label );
extern driver_t *drivers[N_DRIVERS];
extern driver_t maildir_driver, imap_driver, proxy_driver;
+void cleanup_drivers( void );
+
#endif
diff --git a/src/main.c b/src/main.c
@@ -5,9 +5,7 @@
* mbsync - mailbox synchronizer
*/
-#define DEBUG_FLAG DEBUG_MAIN
-
-#include "sync.h"
+#include "main_p.h"
#include <fcntl.h>
#include <signal.h>
@@ -16,9 +14,6 @@
# include <sys/prctl.h>
#endif
-static int chans_total, chans_done;
-static int boxes_total, boxes_done;
-
static void ATTR_NORETURN
version( void )
{
@@ -136,257 +131,12 @@ crashHandler( int n )
}
#endif
-void
-stats( void )
-{
- char buf[3][64];
- char *cs;
- int t, l, ll, cls;
- static int cols = -1;
-
- if (!(DFlags & PROGRESS))
- return;
-
- if (cols < 0 && (!(cs = getenv( "COLUMNS" )) || !(cols = atoi( cs ))))
- cols = 80;
- ll = sprintf( buf[2], "C: %d/%d B: %d/%d", chans_done, chans_total, boxes_done, boxes_total );
- cls = (cols - ll - 10) / 2;
- for (t = 0; t < 2; t++) {
- l = sprintf( buf[t], "+%d/%d *%d/%d #%d/%d",
- new_done[t], new_total[t],
- flags_done[t], flags_total[t],
- trash_done[t], trash_total[t] );
- if (l > cls)
- buf[t][cls - 1] = '~';
- }
- progress( "\r%s F: %.*s N: %.*s", buf[2], cls, buf[0], cls, buf[1] );
-}
-
-static int
-matches( const char *t, const char *p )
-{
- for (;;) {
- if (!*p)
- return !*t;
- if (*p == '*') {
- p++;
- do {
- if (matches( t, p ))
- return 1;
- } while (*t++);
- return 0;
- } else if (*p == '%') {
- p++;
- do {
- if (*t == '/')
- return 0;
- if (matches( t, p ))
- return 1;
- } while (*t++);
- return 0;
- } else {
- if (*p != *t)
- return 0;
- p++, t++;
- }
- }
-}
-
-
-static int
-is_inbox( const char *name )
-{
- return starts_with( name, -1, "INBOX", 5 ) && (!name[5] || name[5] == '/');
-}
-
-static int
-cmp_box_names( const void *a, const void *b )
-{
- const char *as = *(const char * const *)a;
- const char *bs = *(const char * const *)b;
- int ai = is_inbox( as );
- int bi = is_inbox( bs );
- int di = bi - ai;
- if (di)
- return di;
- return strcmp( as, bs );
-}
-
-static char **
-filter_boxes( string_list_t *boxes, const char *prefix, string_list_t *patterns )
-{
- string_list_t *cpat;
- char **boxarr = NULL;
- const char *ps;
- uint not, fnot, pfxl, num = 0, rnum = 0;
-
- pfxl = prefix ? strlen( prefix ) : 0;
- for (; boxes; boxes = boxes->next) {
- if (!starts_with( boxes->string, -1, prefix, pfxl ))
- continue;
- fnot = 1;
- for (cpat = patterns; cpat; cpat = cpat->next) {
- ps = cpat->string;
- if (*ps == '!') {
- ps++;
- not = 1;
- } else {
- not = 0;
- }
- if (matches( boxes->string + pfxl, ps )) {
- fnot = not;
- break;
- }
- }
- if (!fnot) {
- if (num + 1 >= rnum)
- boxarr = nfrealloc( boxarr, (rnum = (rnum + 10) * 2) * sizeof(*boxarr) );
- boxarr[num++] = nfstrdup( boxes->string + pfxl );
- boxarr[num] = NULL;
- }
- }
- qsort( boxarr, num, sizeof(*boxarr), cmp_box_names );
- return boxarr;
-}
-
-static void
-merge_actions( channel_conf_t *chan, int ops[], int have, int mask, int def )
-{
- if (ops[F] & have) {
- chan->ops[F] &= ~mask;
- chan->ops[F] |= ops[F] & mask;
- chan->ops[N] &= ~mask;
- chan->ops[N] |= ops[N] & mask;
- } else if (!(chan->ops[F] & have)) {
- if (global_conf.ops[F] & have) {
- chan->ops[F] |= global_conf.ops[F] & mask;
- chan->ops[N] |= global_conf.ops[N] & mask;
- } else {
- chan->ops[F] |= def;
- chan->ops[N] |= def;
- }
- }
-}
-
-typedef struct box_ent {
- struct box_ent *next;
- char *name;
- int present[2];
-} box_ent_t;
-
-typedef struct chan_ent {
- struct chan_ent *next;
- channel_conf_t *conf;
- box_ent_t *boxes;
- char boxlist;
-} chan_ent_t;
-
-static chan_ent_t *
-add_channel( chan_ent_t ***chanapp, channel_conf_t *chan, int ops[] )
-{
- chan_ent_t *ce = nfzalloc( sizeof(*ce) );
- ce->conf = chan;
-
- merge_actions( chan, ops, XOP_HAVE_TYPE, OP_MASK_TYPE, OP_MASK_TYPE );
- merge_actions( chan, ops, XOP_HAVE_CREATE, OP_CREATE, 0 );
- merge_actions( chan, ops, XOP_HAVE_REMOVE, OP_REMOVE, 0 );
- merge_actions( chan, ops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 );
-
- **chanapp = ce;
- *chanapp = &ce->next;
- chans_total++;
- return ce;
-}
-
-static chan_ent_t *
-add_named_channel( chan_ent_t ***chanapp, char *channame, int ops[] )
-{
- channel_conf_t *chan;
- chan_ent_t *ce;
- box_ent_t *boxes = NULL, **mboxapp = &boxes, *mbox;
- char *boxp, *nboxp;
- size_t boxl;
- char boxlist = 0;
-
- if ((boxp = strchr( channame, ':' )))
- *boxp++ = 0;
- for (chan = channels; chan; chan = chan->next)
- if (!strcmp( chan->name, channame ))
- goto gotchan;
- error( "No channel or group named '%s' defined.\n", channame );
- return NULL;
- gotchan:
- if (boxp) {
- if (!chan->patterns) {
- error( "Cannot override mailbox in channel '%s' - no Patterns.\n", channame );
- return NULL;
- }
- boxlist = 1;
- do {
- nboxp = strpbrk( boxp, ",\n" );
- if (nboxp) {
- boxl = (size_t)(nboxp - boxp);
- *nboxp++ = 0;
- } else {
- boxl = strlen( boxp );
- }
- mbox = nfmalloc( sizeof(*mbox) );
- if (boxl)
- mbox->name = nfstrndup( boxp, boxl );
- else
- mbox->name = nfstrndup( "INBOX", 5 );
- mbox->present[F] = mbox->present[N] = BOX_POSSIBLE;
- mbox->next = NULL;
- *mboxapp = mbox;
- mboxapp = &mbox->next;
- boxes_total++;
- boxp = nboxp;
- } while (boxp);
- } else {
- if (!chan->patterns)
- boxes_total++;
- }
-
- ce = add_channel( chanapp, chan, ops );
- ce->boxes = boxes;
- ce->boxlist = boxlist;
- return ce;
-}
-
-typedef struct {
- int t[2];
- channel_conf_t *chan;
- driver_t *drv[2];
- store_t *ctx[2];
- chan_ent_t *chanptr;
- box_ent_t *boxptr;
- string_list_t *boxes[2];
- char *names[2];
- int ret, all, list, state[2];
- char done, skip, cben;
-} main_vars_t;
-
-#define AUX &mvars->t[t]
-#define MVARS(aux) \
- int t = *(int *)aux; \
- main_vars_t *mvars = (main_vars_t *)(((char *)(&((int *)aux)[-t])) - offsetof(main_vars_t, t));
-
-#define E_START 0
-#define E_OPEN 1
-#define E_SYNC 2
-
-static void sync_chans( main_vars_t *mvars, int ent );
-
int
main( int argc, char **argv )
{
- main_vars_t mvars[1];
- chan_ent_t *chans = NULL, **chanapp = &chans;
- group_conf_t *group;
- channel_conf_t *chan;
- string_list_t *channame;
+ core_vars_t mvars[1];
char *config = NULL, *opt, *ochar;
- int oind, cops = 0, op, ops[2] = { 0, 0 }, ms_warn = 0;
+ int oind, cops = 0, op, ms_warn = 0;
tzset();
gethostname( Hostname, sizeof(Hostname) );
@@ -400,7 +150,6 @@ main( int argc, char **argv )
arc4_init();
memset( mvars, 0, sizeof(*mvars) );
- mvars->t[1] = 1;
for (oind = 1, ochar = NULL; ; ) {
if (!ochar || !*ochar) {
@@ -457,9 +206,9 @@ main( int argc, char **argv )
goto badopt;
DFlags |= op;
} else if (!strcmp( opt, "pull" )) {
- cops |= XOP_PULL, ops[F] |= XOP_HAVE_TYPE;
+ cops |= XOP_PULL, mvars->ops[F] |= XOP_HAVE_TYPE;
} else if (!strcmp( opt, "push" )) {
- cops |= XOP_PUSH, ops[F] |= XOP_HAVE_TYPE;
+ cops |= XOP_PUSH, mvars->ops[F] |= XOP_HAVE_TYPE;
} else if (starts_with( opt, -1, "create", 6 )) {
opt += 6;
op = OP_CREATE|XOP_HAVE_CREATE;
@@ -467,16 +216,16 @@ main( int argc, char **argv )
if (!*opt)
cops |= op;
else if (!strcmp( opt, "-far" ))
- ops[F] |= op;
+ mvars->ops[F] |= op;
else if (!strcmp( opt, "-master" )) // Pre-1.4 legacy
- ops[F] |= op, ms_warn = 1;
+ mvars->ops[F] |= op, ms_warn = 1;
else if (!strcmp( opt, "-near" ))
- ops[N] |= op;
+ mvars->ops[N] |= op;
else if (!strcmp( opt, "-slave" )) // Pre-1.4 legacy
- ops[N] |= op, ms_warn = 1;
+ mvars->ops[N] |= op, ms_warn = 1;
else
goto badopt;
- ops[F] |= op & (XOP_HAVE_CREATE|XOP_HAVE_REMOVE|XOP_HAVE_EXPUNGE);
+ mvars->ops[F] |= op & (XOP_HAVE_CREATE | XOP_HAVE_REMOVE | XOP_HAVE_EXPUNGE);
} else if (starts_with( opt, -1, "remove", 6 )) {
opt += 6;
op = OP_REMOVE|XOP_HAVE_REMOVE;
@@ -486,15 +235,15 @@ main( int argc, char **argv )
op = OP_EXPUNGE|XOP_HAVE_EXPUNGE;
goto lcop;
} else if (!strcmp( opt, "no-expunge" )) {
- ops[F] |= XOP_HAVE_EXPUNGE;
+ mvars->ops[F] |= XOP_HAVE_EXPUNGE;
} else if (!strcmp( opt, "no-create" )) {
- ops[F] |= XOP_HAVE_CREATE;
+ mvars->ops[F] |= XOP_HAVE_CREATE;
} else if (!strcmp( opt, "no-remove" )) {
- ops[F] |= XOP_HAVE_REMOVE;
+ mvars->ops[F] |= XOP_HAVE_REMOVE;
} else if (!strcmp( opt, "full" )) {
- ops[F] |= XOP_HAVE_TYPE|XOP_PULL|XOP_PUSH;
+ mvars->ops[F] |= XOP_HAVE_TYPE | XOP_PULL | XOP_PUSH;
} else if (!strcmp( opt, "noop" )) {
- ops[F] |= XOP_HAVE_TYPE;
+ mvars->ops[F] |= XOP_HAVE_TYPE;
} else if (starts_with( opt, -1, "pull", 4 )) {
op = XOP_PULL;
lcac:
@@ -527,11 +276,11 @@ main( int argc, char **argv )
return 1;
}
switch (op & XOP_MASK_DIR) {
- case XOP_PULL: ops[N] |= op & OP_MASK_TYPE; break;
- case XOP_PUSH: ops[F] |= op & OP_MASK_TYPE; break;
+ case XOP_PULL: mvars->ops[N] |= op & OP_MASK_TYPE; break;
+ case XOP_PUSH: mvars->ops[F] |= op & OP_MASK_TYPE; break;
default: cops |= op; break;
}
- ops[F] |= XOP_HAVE_TYPE;
+ mvars->ops[F] |= XOP_HAVE_TYPE;
}
continue;
}
@@ -559,18 +308,18 @@ main( int argc, char **argv )
op = OP_CREATE|XOP_HAVE_CREATE;
cop:
if (*ochar == 'f')
- ops[F] |= op, ochar++;
+ mvars->ops[F] |= op, ochar++;
else if (*ochar == 'm') // Pre-1.4 legacy
- ops[F] |= op, ms_warn = 1, ochar++;
+ mvars->ops[F] |= op, ms_warn = 1, ochar++;
else if (*ochar == 'n')
- ops[N] |= op, ochar++;
+ mvars->ops[N] |= op, ochar++;
else if (*ochar == 's') // Pre-1.4 legacy
- ops[N] |= op, ms_warn = 1, ochar++;
+ mvars->ops[N] |= op, ms_warn = 1, ochar++;
else if (*ochar == '-')
ochar++;
else
cops |= op;
- ops[F] |= op & (XOP_HAVE_CREATE|XOP_HAVE_REMOVE|XOP_HAVE_EXPUNGE);
+ mvars->ops[F] |= op & (XOP_HAVE_CREATE | XOP_HAVE_REMOVE | XOP_HAVE_EXPUNGE);
break;
case 'R':
op = OP_REMOVE|XOP_HAVE_REMOVE;
@@ -582,7 +331,7 @@ main( int argc, char **argv )
cops |= XOP_PULL|XOP_PUSH;
FALLTHROUGH
case '0':
- ops[F] |= XOP_HAVE_TYPE;
+ mvars->ops[F] |= XOP_HAVE_TYPE;
break;
case 'n':
case 'd':
@@ -605,14 +354,14 @@ main( int argc, char **argv )
}
if (op & OP_MASK_TYPE) {
switch (op & XOP_MASK_DIR) {
- case XOP_PULL: ops[N] |= op & OP_MASK_TYPE; break;
- case XOP_PUSH: ops[F] |= op & OP_MASK_TYPE; break;
+ case XOP_PULL: mvars->ops[N] |= op & OP_MASK_TYPE; break;
+ case XOP_PUSH: mvars->ops[F] |= op & OP_MASK_TYPE; break;
default: cops |= op; break;
}
} else {
cops |= op;
}
- ops[F] |= XOP_HAVE_TYPE;
+ mvars->ops[F] |= XOP_HAVE_TYPE;
break;
case 'L':
op = XOP_PULL;
@@ -713,406 +462,12 @@ main( int argc, char **argv )
}
#endif
- if (merge_ops( cops, ops ))
+ if (merge_ops( cops, mvars->ops ))
return 1;
if (load_config( config ))
return 1;
- if (!channels) {
- fputs( "No channels defined. Try 'man " EXE "'\n", stderr );
- return 1;
- }
-
- if (mvars->all) {
- for (chan = channels; chan; chan = chan->next) {
- add_channel( &chanapp, chan, ops );
- if (!chan->patterns)
- boxes_total++;
- }
- } else {
- for (; argv[oind]; oind++) {
- for (group = groups; group; group = group->next) {
- if (!strcmp( group->name, argv[oind] )) {
- for (channame = group->channels; channame; channame = channame->next)
- if (!add_named_channel( &chanapp, channame->string, ops ))
- mvars->ret = 1;
- goto gotgrp;
- }
- }
- if (!add_named_channel( &chanapp, argv[oind], ops ))
- mvars->ret = 1;
- gotgrp: ;
- }
- }
- if (!chans) {
- fputs( "No channel specified. Try '" EXE " -h'\n", stderr );
- return 1;
- }
- mvars->chanptr = chans;
-
- if (!mvars->list)
- stats();
- mvars->cben = 1;
- sync_chans( mvars, E_START );
- main_loop();
- if (!mvars->list)
- flushn();
+ sync_chans( mvars, argv + oind );
return mvars->ret;
}
-
-#define ST_FRESH 0
-#define ST_CONNECTED 1
-#define ST_OPEN 2
-#define ST_CANCELING 3
-#define ST_CLOSED 4
-
-static void
-cancel_prep_done( void *aux )
-{
- MVARS(aux)
-
- mvars->drv[t]->free_store( mvars->ctx[t] );
- mvars->state[t] = ST_CLOSED;
- sync_chans( mvars, E_OPEN );
-}
-
-static void
-store_bad( void *aux )
-{
- MVARS(aux)
-
- mvars->drv[t]->cancel_store( mvars->ctx[t] );
- mvars->state[t] = ST_CLOSED;
- mvars->ret = mvars->skip = 1;
- sync_chans( mvars, E_OPEN );
-}
-
-static void store_connected( int sts, void *aux );
-static void store_listed( int sts, string_list_t *boxes, void *aux );
-static int sync_listed_boxes( main_vars_t *mvars, box_ent_t *mbox );
-static void done_sync_2_dyn( int sts, void *aux );
-static void done_sync( int sts, void *aux );
-
-#define nz(a,b) ((a)?(a):(b))
-
-static void
-sync_chans( main_vars_t *mvars, int ent )
-{
- box_ent_t *mbox, *nmbox, **mboxapp;
- char **boxes[2];
- const char *labels[2];
- int t, mb, sb, cmp;
-
- if (!mvars->cben)
- return;
- switch (ent) {
- case E_OPEN: goto opened;
- case E_SYNC: goto syncone;
- }
- do {
- mvars->chan = mvars->chanptr->conf;
- info( "Channel %s\n", mvars->chan->name );
- mvars->skip = mvars->cben = 0;
- for (t = 0; t < 2; t++) {
- int st = mvars->chan->stores[t]->driver->get_fail_state( mvars->chan->stores[t] );
- if (st != FAIL_TEMP) {
- info( "Skipping due to %sfailed %s store %s.\n",
- (st == FAIL_WAIT) ? "temporarily " : "", str_fn[t], mvars->chan->stores[t]->name );
- mvars->skip = 1;
- }
- }
- if (mvars->skip)
- goto next2;
- mvars->state[F] = mvars->state[N] = ST_FRESH;
- uint dcaps[2];
- for (t = 0; t < 2; t++) {
- mvars->drv[t] = mvars->chan->stores[t]->driver;
- dcaps[t] = mvars->drv[t]->get_caps( NULL );
- }
- if ((DFlags & DEBUG_DRV) || (dcaps[F] & dcaps[N] & DRV_VERBOSE))
- labels[F] = "F: ", labels[N] = "N: ";
- else
- labels[F] = labels[N] = "";
- for (t = 0; t < 2; t++) {
- store_t *ctx = mvars->drv[t]->alloc_store( mvars->chan->stores[t], labels[t] );
- if ((DFlags & DEBUG_DRV) || ((DFlags & FORCEASYNC) && !(dcaps[t] & DRV_ASYNC))) {
- mvars->drv[t] = &proxy_driver;
- ctx = proxy_alloc_store( ctx, labels[t] );
- }
- mvars->ctx[t] = ctx;
- mvars->drv[t]->set_bad_callback( ctx, store_bad, AUX );
- }
- for (t = 0; ; t++) {
- 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 );
- if (t || mvars->skip)
- break;
- }
-
- mvars->cben = 1;
- opened:
- if (mvars->skip)
- goto next;
- if (mvars->state[F] != ST_OPEN || mvars->state[N] != ST_OPEN)
- return;
-
- if (!mvars->chanptr->boxlist && mvars->chan->patterns) {
- mvars->chanptr->boxlist = 2;
- boxes[F] = filter_boxes( mvars->boxes[F], mvars->chan->boxes[F], mvars->chan->patterns );
- boxes[N] = filter_boxes( mvars->boxes[N], mvars->chan->boxes[N], mvars->chan->patterns );
- mboxapp = &mvars->chanptr->boxes;
- for (mb = sb = 0; ; ) {
- char *mname = boxes[F] ? boxes[F][mb] : NULL;
- char *sname = boxes[N] ? boxes[N][sb] : NULL;
- if (!mname && !sname)
- break;
- mbox = nfmalloc( sizeof(*mbox) );
- if (!(cmp = !mname - !sname) && !(cmp = cmp_box_names( &mname, &sname ))) {
- mbox->name = mname;
- free( sname );
- mbox->present[F] = mbox->present[N] = BOX_PRESENT;
- mb++;
- sb++;
- } else if (cmp < 0) {
- mbox->name = mname;
- mbox->present[F] = BOX_PRESENT;
- mbox->present[N] = (!mb && !strcmp( mbox->name, "INBOX" )) ? BOX_PRESENT : BOX_ABSENT;
- mb++;
- } else {
- mbox->name = sname;
- mbox->present[F] = (!sb && !strcmp( mbox->name, "INBOX" )) ? BOX_PRESENT : BOX_ABSENT;
- mbox->present[N] = BOX_PRESENT;
- sb++;
- }
- mbox->next = NULL;
- *mboxapp = mbox;
- mboxapp = &mbox->next;
- boxes_total++;
- }
- free( boxes[F] );
- free( boxes[N] );
- if (!mvars->list)
- stats();
- }
- mvars->boxptr = mvars->chanptr->boxes;
-
- if (mvars->list && chans_total > 1)
- printf( "%s:\n", mvars->chan->name );
- syncml:
- mvars->done = mvars->cben = 0;
- if (mvars->chanptr->boxlist) {
- while ((mbox = mvars->boxptr)) {
- mvars->boxptr = mbox->next;
- if (sync_listed_boxes( mvars, mbox ))
- goto syncw;
- }
- } else {
- if (!mvars->list) {
- int present[] = { BOX_POSSIBLE, BOX_POSSIBLE };
- sync_boxes( mvars->ctx, mvars->chan->boxes, present, mvars->chan, done_sync, mvars );
- mvars->skip = 1;
- syncw:
- mvars->cben = 1;
- if (!mvars->done)
- return;
- syncone:
- if (!mvars->skip)
- goto syncml;
- } else {
- printf( "%s <=> %s\n", nz( mvars->chan->boxes[F], "INBOX" ), nz( mvars->chan->boxes[N], "INBOX" ) );
- }
- }
-
- next:
- mvars->cben = 0;
- for (t = 0; t < 2; t++) {
- free_string_list( mvars->boxes[t] );
- mvars->boxes[t] = NULL;
- if (mvars->state[t] == ST_FRESH) {
- /* An unconnected store may be only cancelled. */
- mvars->state[t] = ST_CLOSED;
- mvars->drv[t]->cancel_store( mvars->ctx[t] );
- } else if (mvars->state[t] == ST_CONNECTED || mvars->state[t] == ST_OPEN) {
- mvars->state[t] = ST_CANCELING;
- mvars->drv[t]->cancel_cmds( mvars->ctx[t], cancel_prep_done, AUX );
- }
- }
- mvars->cben = 1;
- if (mvars->state[F] != ST_CLOSED || mvars->state[N] != ST_CLOSED) {
- mvars->skip = 1;
- return;
- }
- if (mvars->chanptr->boxlist) {
- for (nmbox = mvars->chanptr->boxes; (mbox = nmbox); ) {
- nmbox = mbox->next;
- free( mbox->name );
- free( mbox );
- }
- mvars->chanptr->boxes = NULL;
- mvars->chanptr->boxlist = 0;
- }
- next2:
- if (!mvars->list) {
- chans_done++;
- stats();
- }
- chan_ent_t *nchan = mvars->chanptr->next;
- free( mvars->chanptr );
- mvars->chanptr = nchan;
- } while (mvars->chanptr);
- for (t = 0; t < N_DRIVERS; t++)
- drivers[t]->cleanup();
-}
-
-static void
-store_connected( int sts, void *aux )
-{
- MVARS(aux)
- string_list_t *cpat;
- int cflags;
-
- switch (sts) {
- case DRV_CANCELED:
- return;
- case DRV_OK:
- if (!mvars->skip && !mvars->chanptr->boxlist && mvars->chan->patterns) {
- for (cflags = 0, cpat = mvars->chan->patterns; cpat; cpat = cpat->next) {
- const char *pat = cpat->string;
- if (*pat != '!') {
- char buf[8];
- int bufl = snprintf( buf, sizeof(buf), "%s%s", nz( mvars->chan->boxes[t], "" ), pat );
- int flags = 0;
- /* Partial matches like "INB*" or even "*" are not considered,
- * except implicity when the INBOX lives under Path. */
- if (starts_with( buf, bufl, "INBOX", 5 )) {
- char c = buf[5];
- if (!c) {
- /* User really wants the INBOX. */
- flags |= LIST_INBOX;
- } else if (c == '/') {
- /* Flattened sub-folders of INBOX actually end up in Path. */
- if (mvars->ctx[t]->conf->flat_delim[0])
- flags |= LIST_PATH;
- else
- flags |= LIST_INBOX;
- } else if (c == '*' || c == '%') {
- /* It can be both INBOX and Path, but don't require Path to be configured. */
- flags |= LIST_INBOX | LIST_PATH_MAYBE;
- } else {
- /* It's definitely not the INBOX. */
- flags |= LIST_PATH;
- }
- } else {
- flags |= LIST_PATH;
- }
- debug( "pattern '%s' (effective '%s'): %sPath, %sINBOX\n",
- pat, buf, (flags & LIST_PATH) ? "" : "no ", (flags & LIST_INBOX) ? "" : "no ");
- cflags |= flags;
- }
- }
- mvars->state[t] = ST_CONNECTED;
- mvars->drv[t]->list_store( mvars->ctx[t], cflags, store_listed, AUX );
- return;
- }
- mvars->state[t] = ST_OPEN;
- break;
- default:
- mvars->ret = mvars->skip = 1;
- mvars->state[t] = ST_OPEN;
- break;
- }
- sync_chans( mvars, E_OPEN );
-}
-
-static void
-store_listed( int sts, string_list_t *boxes, void *aux )
-{
- MVARS(aux)
- string_list_t *box;
-
- switch (sts) {
- case DRV_CANCELED:
- return;
- case DRV_OK:
- for (box = boxes; box; box = box->next) {
- if (mvars->ctx[t]->conf->flat_delim[0]) {
- string_list_t *nbox;
- if (map_name( box->string, (char **)&nbox, offsetof(string_list_t, string), mvars->ctx[t]->conf->flat_delim, "/" ) < 0) {
- error( "Error: flattened mailbox name '%s' contains canonical hierarchy delimiter\n", box->string );
- mvars->ret = mvars->skip = 1;
- } else {
- nbox->next = mvars->boxes[t];
- mvars->boxes[t] = nbox;
- }
- } else {
- add_string_list( &mvars->boxes[t], box->string );
- }
- }
- if (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 );
- }
- break;
- default:
- mvars->ret = mvars->skip = 1;
- break;
- }
- mvars->state[t] = ST_OPEN;
- sync_chans( mvars, E_OPEN );
-}
-
-static int
-sync_listed_boxes( main_vars_t *mvars, box_ent_t *mbox )
-{
- if (mvars->chan->boxes[F] || mvars->chan->boxes[N]) {
- const char *mpfx = nz( mvars->chan->boxes[F], "" );
- const char *spfx = nz( mvars->chan->boxes[N], "" );
- if (!mvars->list) {
- nfasprintf( &mvars->names[F], "%s%s", mpfx, 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 );
- return 1;
- }
- printf( "%s%s <=> %s%s\n", mpfx, mbox->name, spfx, mbox->name );
- } else {
- if (!mvars->list) {
- mvars->names[F] = mvars->names[N] = mbox->name;
- sync_boxes( mvars->ctx, (const char * const *)mvars->names, mbox->present, mvars->chan, done_sync, mvars );
- return 1;
- }
- puts( mbox->name );
- }
- return 0;
-}
-
-static void
-done_sync_2_dyn( int sts, void *aux )
-{
- main_vars_t *mvars = (main_vars_t *)aux;
-
- free( mvars->names[F] );
- free( mvars->names[N] );
- done_sync( sts, aux );
-}
-
-static void
-done_sync( int sts, void *aux )
-{
- main_vars_t *mvars = (main_vars_t *)aux;
-
- mvars->done = 1;
- boxes_done++;
- stats();
- if (sts) {
- mvars->ret = 1;
- if (sts & (SYNC_BAD(F) | SYNC_BAD(N))) {
- if (sts & SYNC_BAD(F))
- mvars->state[F] = ST_CLOSED;
- if (sts & SYNC_BAD(N))
- mvars->state[N] = ST_CLOSED;
- mvars->skip = 1;
- }
- }
- sync_chans( mvars, E_SYNC );
-}
diff --git a/src/main_p.h b/src/main_p.h
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
+// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
+//
+// mbsync - mailbox synchronizer
+//
+
+#ifndef MAIN_P_H
+#define MAIN_P_H
+
+#define DEBUG_FLAG DEBUG_MAIN
+
+#include "sync.h"
+
+typedef struct {
+ int ret;
+ int all;
+ int list;
+ int list_stores;
+ int ops[2];
+} core_vars_t;
+
+void sync_chans( core_vars_t *cvars, char **argv );
+
+#endif
diff --git a/src/main_sync.c b/src/main_sync.c
@@ -0,0 +1,692 @@
+// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
+// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
+// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
+//
+// mbsync - mailbox synchronizer
+//
+
+#include "main_p.h"
+
+#define nz(a, b) ((a) ? (a) : (b))
+
+static int chans_total, chans_done;
+static int boxes_total, boxes_done;
+
+void
+stats( void )
+{
+ char buf[3][64];
+ char *cs;
+ static int cols = -1;
+
+ if (!(DFlags & PROGRESS))
+ return;
+
+ if (cols < 0 && (!(cs = getenv( "COLUMNS" )) || !(cols = atoi( cs ))))
+ cols = 80;
+ int ll = sprintf( buf[2], "C: %d/%d B: %d/%d", chans_done, chans_total, boxes_done, boxes_total );
+ int cls = (cols - ll - 10) / 2;
+ for (int t = 0; t < 2; t++) {
+ int l = sprintf( buf[t], "+%d/%d *%d/%d #%d/%d",
+ new_done[t], new_total[t],
+ flags_done[t], flags_total[t],
+ trash_done[t], trash_total[t] );
+ if (l > cls)
+ buf[t][cls - 1] = '~';
+ }
+ progress( "\r%s F: %.*s N: %.*s", buf[2], cls, buf[0], cls, buf[1] );
+}
+
+static int
+matches( const char *t, const char *p )
+{
+ for (;;) {
+ if (!*p)
+ return !*t;
+ if (*p == '*') {
+ p++;
+ do {
+ if (matches( t, p ))
+ return 1;
+ } while (*t++);
+ return 0;
+ } else if (*p == '%') {
+ p++;
+ do {
+ if (*t == '/')
+ return 0;
+ if (matches( t, p ))
+ return 1;
+ } while (*t++);
+ return 0;
+ } else {
+ if (*p != *t)
+ return 0;
+ p++, t++;
+ }
+ }
+}
+
+
+static int
+is_inbox( const char *name )
+{
+ return starts_with( name, -1, "INBOX", 5 ) && (!name[5] || name[5] == '/');
+}
+
+static int
+cmp_box_names( const void *a, const void *b )
+{
+ const char *as = *(const char * const *)a;
+ const char *bs = *(const char * const *)b;
+ int ai = is_inbox( as );
+ int bi = is_inbox( bs );
+ int di = bi - ai;
+ if (di)
+ return di;
+ return strcmp( as, bs );
+}
+
+static char **
+filter_boxes( string_list_t *boxes, const char *prefix, string_list_t *patterns )
+{
+ char **boxarr = NULL;
+ uint num = 0, rnum = 0;
+
+ uint pfxl = prefix ? strlen( prefix ) : 0;
+ for (; boxes; boxes = boxes->next) {
+ if (!starts_with( boxes->string, -1, prefix, pfxl ))
+ continue;
+ uint fnot = 1, not;
+ for (string_list_t *cpat = patterns; cpat; cpat = cpat->next) {
+ const char *ps = cpat->string;
+ if (*ps == '!') {
+ ps++;
+ not = 1;
+ } else {
+ not = 0;
+ }
+ if (matches( boxes->string + pfxl, ps )) {
+ fnot = not;
+ break;
+ }
+ }
+ if (!fnot) {
+ if (num + 1 >= rnum)
+ boxarr = nfrealloc( boxarr, (rnum = (rnum + 10) * 2) * sizeof(*boxarr) );
+ boxarr[num++] = nfstrdup( boxes->string + pfxl );
+ boxarr[num] = NULL;
+ }
+ }
+ qsort( boxarr, num, sizeof(*boxarr), cmp_box_names );
+ return boxarr;
+}
+
+static void
+merge_actions( channel_conf_t *chan, int ops[], int have, int mask, int def )
+{
+ if (ops[F] & have) {
+ chan->ops[F] &= ~mask;
+ chan->ops[F] |= ops[F] & mask;
+ chan->ops[N] &= ~mask;
+ chan->ops[N] |= ops[N] & mask;
+ } else if (!(chan->ops[F] & have)) {
+ if (global_conf.ops[F] & have) {
+ chan->ops[F] |= global_conf.ops[F] & mask;
+ chan->ops[N] |= global_conf.ops[N] & mask;
+ } else {
+ chan->ops[F] |= def;
+ chan->ops[N] |= def;
+ }
+ }
+}
+
+typedef struct box_ent {
+ struct box_ent *next;
+ char *name;
+ int present[2];
+} box_ent_t;
+
+typedef struct chan_ent {
+ struct chan_ent *next;
+ channel_conf_t *conf;
+ box_ent_t *boxes;
+ int boxlist;
+} chan_ent_t;
+
+static chan_ent_t *
+add_channel( chan_ent_t ***chanapp, channel_conf_t *chan, int ops[] )
+{
+ chan_ent_t *ce = nfzalloc( sizeof(*ce) );
+ ce->conf = chan;
+
+ merge_actions( chan, ops, XOP_HAVE_TYPE, OP_MASK_TYPE, OP_MASK_TYPE );
+ merge_actions( chan, ops, XOP_HAVE_CREATE, OP_CREATE, 0 );
+ merge_actions( chan, ops, XOP_HAVE_REMOVE, OP_REMOVE, 0 );
+ merge_actions( chan, ops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 );
+
+ **chanapp = ce;
+ *chanapp = &ce->next;
+ chans_total++;
+ return ce;
+}
+
+static chan_ent_t *
+add_named_channel( chan_ent_t ***chanapp, char *channame, int ops[] )
+{
+ box_ent_t *boxes = NULL, **mboxapp = &boxes, *mbox;
+ int boxlist = 0;
+
+ char *boxp;
+ if ((boxp = strchr( channame, ':' )))
+ *boxp++ = 0;
+ channel_conf_t *chan;
+ for (chan = channels; chan; chan = chan->next)
+ if (!strcmp( chan->name, channame ))
+ goto gotchan;
+ error( "No channel or group named '%s' defined.\n", channame );
+ return NULL;
+ gotchan:
+ if (boxp) {
+ if (!chan->patterns) {
+ error( "Cannot override mailbox in channel '%s' - no Patterns.\n", channame );
+ return NULL;
+ }
+ boxlist = 1;
+ do {
+ char *nboxp = strpbrk( boxp, ",\n" );
+ size_t boxl;
+ if (nboxp) {
+ boxl = (size_t)(nboxp - boxp);
+ *nboxp++ = 0;
+ } else {
+ boxl = strlen( boxp );
+ }
+ mbox = nfmalloc( sizeof(*mbox) );
+ if (boxl)
+ mbox->name = nfstrndup( boxp, boxl );
+ else
+ mbox->name = nfstrndup( "INBOX", 5 );
+ mbox->present[F] = mbox->present[N] = BOX_POSSIBLE;
+ mbox->next = NULL;
+ *mboxapp = mbox;
+ mboxapp = &mbox->next;
+ boxes_total++;
+ boxp = nboxp;
+ } while (boxp);
+ } else {
+ if (!chan->patterns)
+ boxes_total++;
+ }
+
+ chan_ent_t *ce = add_channel( chanapp, chan, ops );
+ ce->boxes = boxes;
+ ce->boxlist = boxlist;
+ return ce;
+}
+
+typedef struct {
+ int t[2];
+ core_vars_t *cvars;
+ channel_conf_t *chan;
+ driver_t *drv[2];
+ store_t *ctx[2];
+ chan_ent_t *chanptr;
+ box_ent_t *boxptr;
+ string_list_t *boxes[2];
+ char *names[2];
+ int state[2];
+ int chan_cben, fnlz_cben, box_cben, box_done;
+} main_vars_t;
+
+#define AUX &mvars->t[t]
+#define MVARS(aux) \
+ int t = *(int *)aux; \
+ main_vars_t *mvars = (main_vars_t *)(((char *)(&((int *)aux)[-t])) - offsetof(main_vars_t, t));
+
+static void do_sync_chans( main_vars_t *lvars );
+
+void
+sync_chans( core_vars_t *cvars, char **argv )
+{
+ main_vars_t mvars[1];
+ chan_ent_t *chans = NULL, **chanapp = &chans;
+
+ memset( mvars, 0, sizeof(*mvars) );
+ mvars->t[1] = 1;
+ mvars->cvars = cvars;
+
+ if (!channels) {
+ fputs( "No channels defined. Try 'man " EXE "'\n", stderr );
+ cvars->ret = 1;
+ return;
+ }
+
+ if (cvars->all) {
+ for (channel_conf_t *chan = channels; chan; chan = chan->next) {
+ add_channel( &chanapp, chan, cvars->ops );
+ if (!chan->patterns)
+ boxes_total++;
+ }
+ } else {
+ for (; *argv; argv++) {
+ for (group_conf_t *group = groups; group; group = group->next) {
+ if (!strcmp( group->name, *argv )) {
+ for (string_list_t *channame = group->channels; channame; channame = channame->next)
+ if (!add_named_channel( &chanapp, channame->string, cvars->ops ))
+ cvars->ret = 1;
+ goto gotgrp;
+ }
+ }
+ if (!add_named_channel( &chanapp, *argv, cvars->ops ))
+ cvars->ret = 1;
+ gotgrp: ;
+ }
+ }
+ if (!chans) {
+ fputs( "No channel specified. Try '" EXE " -h'\n", stderr );
+ cvars->ret = 1;
+ return;
+ }
+ mvars->chanptr = chans;
+
+ if (!cvars->list)
+ stats();
+ do_sync_chans( mvars );
+ main_loop();
+ if (!cvars->list)
+ flushn();
+}
+
+enum {
+ ST_FRESH,
+ ST_CONNECTED,
+ ST_OPEN,
+ ST_CANCELING,
+ ST_CLOSED,
+};
+
+static int
+check_cancel( main_vars_t *mvars )
+{
+ return mvars->state[F] >= ST_CANCELING || mvars->state[N] >= ST_CANCELING;
+}
+
+static void store_connected( int sts, void *aux );
+static void store_listed( int sts, string_list_t *boxes, void *aux );
+static void sync_opened( main_vars_t *mvars, int t );
+static void do_sync_boxes( main_vars_t *lvars );
+static void done_sync_dyn( int sts, void *aux );
+static void done_sync( int sts, void *aux );
+static void finalize_sync( main_vars_t *mvars );
+
+static void
+store_bad( void *aux )
+{
+ MVARS(aux)
+
+ mvars->drv[t]->cancel_store( mvars->ctx[t] );
+ mvars->state[t] = ST_CLOSED;
+ mvars->cvars->ret = 1;
+ finalize_sync( mvars );
+}
+
+static void
+advance_chan( main_vars_t *mvars )
+{
+ if (!mvars->cvars->list) {
+ chans_done++;
+ stats();
+ }
+ chan_ent_t *nchan = mvars->chanptr->next;
+ free( mvars->chanptr );
+ mvars->chanptr = nchan;
+}
+
+static void
+do_sync_chans( main_vars_t *mvars )
+{
+ while (mvars->chanptr) {
+ mvars->chan = mvars->chanptr->conf;
+ info( "Channel %s\n", mvars->chan->name );
+ for (int t = 0; t < 2; t++) {
+ int st = mvars->chan->stores[t]->driver->get_fail_state( mvars->chan->stores[t] );
+ if (st != FAIL_TEMP) {
+ info( "Skipping due to %sfailed %s store %s.\n",
+ (st == FAIL_WAIT) ? "temporarily " : "", str_fn[t], mvars->chan->stores[t]->name );
+ goto next;
+ }
+ }
+
+ uint dcaps[2];
+ for (int t = 0; t < 2; t++) {
+ mvars->drv[t] = mvars->chan->stores[t]->driver;
+ dcaps[t] = mvars->drv[t]->get_caps( NULL );
+ }
+ const char *labels[2];
+ if ((DFlags & DEBUG_DRV) || (dcaps[F] & dcaps[N] & DRV_VERBOSE))
+ labels[F] = "F: ", labels[N] = "N: ";
+ else
+ labels[F] = labels[N] = "";
+ for (int t = 0; t < 2; t++) {
+ store_t *ctx = mvars->drv[t]->alloc_store( mvars->chan->stores[t], labels[t] );
+ if ((DFlags & DEBUG_DRV) || ((DFlags & FORCEASYNC) && !(dcaps[t] & DRV_ASYNC))) {
+ mvars->drv[t] = &proxy_driver;
+ ctx = proxy_alloc_store( ctx, labels[t] );
+ }
+ mvars->ctx[t] = ctx;
+ mvars->drv[t]->set_bad_callback( ctx, store_bad, AUX );
+ mvars->state[t] = ST_FRESH;
+ }
+ mvars->chan_cben = 0;
+ for (int t = 0; ; t++) {
+ 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 );
+ if (t || check_cancel( mvars ))
+ break;
+ }
+ if (mvars->state[F] != ST_CLOSED || mvars->state[N] != ST_CLOSED) {
+ mvars->chan_cben = 1;
+ return;
+ }
+
+ next:
+ advance_chan( mvars );
+ }
+ cleanup_drivers();
+}
+
+static void
+sync_next_chan( main_vars_t *mvars )
+{
+ if (mvars->chan_cben) {
+ advance_chan( mvars );
+ do_sync_chans( mvars );
+ }
+}
+
+static void
+store_connected( int sts, void *aux )
+{
+ MVARS(aux)
+
+ switch (sts) {
+ case DRV_CANCELED:
+ return;
+ case DRV_OK:
+ mvars->state[t] = ST_CONNECTED;
+ if (check_cancel( mvars ))
+ break;
+ if (!mvars->chanptr->boxlist && mvars->chan->patterns) {
+ int cflags = 0;
+ for (string_list_t *cpat = mvars->chan->patterns; cpat; cpat = cpat->next) {
+ const char *pat = cpat->string;
+ if (*pat != '!') {
+ char buf[8];
+ int bufl = snprintf( buf, sizeof(buf), "%s%s", nz( mvars->chan->boxes[t], "" ), pat );
+ int flags = 0;
+ // Partial matches like "INB*" or even "*" are not considered,
+ // except implicity when the INBOX lives under Path.
+ if (starts_with( buf, bufl, "INBOX", 5 )) {
+ char c = buf[5];
+ if (!c) {
+ // User really wants the INBOX.
+ flags |= LIST_INBOX;
+ } else if (c == '/') {
+ // Flattened sub-folders of INBOX actually end up in Path.
+ if (mvars->ctx[t]->conf->flat_delim[0])
+ flags |= LIST_PATH;
+ else
+ flags |= LIST_INBOX;
+ } else if (c == '*' || c == '%') {
+ // It can be both INBOX and Path, but don't require Path to be configured.
+ flags |= LIST_INBOX | LIST_PATH_MAYBE;
+ } else {
+ // It's definitely not the INBOX.
+ flags |= LIST_PATH;
+ }
+ } else {
+ flags |= LIST_PATH;
+ }
+ debug( "pattern '%s' (effective '%s'): %sPath, %sINBOX\n",
+ pat, buf, (flags & LIST_PATH) ? "" : "no ", (flags & LIST_INBOX) ? "" : "no ");
+ cflags |= flags;
+ }
+ }
+ mvars->drv[t]->list_store( mvars->ctx[t], cflags, store_listed, AUX );
+ return;
+ }
+ sync_opened( mvars, t );
+ return;
+ default:
+ mvars->cvars->ret = 1;
+ break;
+ }
+ finalize_sync( mvars );
+}
+
+static void
+store_listed( int sts, string_list_t *boxes, void *aux )
+{
+ MVARS(aux)
+ int fail = 0;
+
+ switch (sts) {
+ case DRV_CANCELED:
+ return;
+ case DRV_OK:
+ if (check_cancel( mvars ))
+ break;
+ for (string_list_t *box = boxes; box; box = box->next) {
+ if (mvars->ctx[t]->conf->flat_delim[0]) {
+ string_list_t *nbox;
+ if (map_name( box->string, (char **)&nbox, offsetof(string_list_t, string), mvars->ctx[t]->conf->flat_delim, "/" ) < 0) {
+ error( "Error: flattened mailbox name '%s' contains canonical hierarchy delimiter\n", box->string );
+ fail = 1;
+ } else {
+ nbox->next = mvars->boxes[t];
+ mvars->boxes[t] = nbox;
+ }
+ } else {
+ add_string_list( &mvars->boxes[t], box->string );
+ }
+ }
+ if (fail) {
+ mvars->cvars->ret = 1;
+ break;
+ }
+ if (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 );
+ }
+ sync_opened( mvars, t );
+ return;
+ default:
+ mvars->cvars->ret = 1;
+ break;
+ }
+ finalize_sync( mvars );
+}
+
+static void
+sync_opened( main_vars_t *mvars, int t )
+{
+ mvars->state[t] = ST_OPEN;
+ if (mvars->state[t^1] != ST_OPEN)
+ return;
+
+ if (!mvars->chanptr->boxlist && mvars->chan->patterns) {
+ mvars->chanptr->boxlist = 2;
+ char **boxes[2];
+ boxes[F] = filter_boxes( mvars->boxes[F], mvars->chan->boxes[F], mvars->chan->patterns );
+ boxes[N] = filter_boxes( mvars->boxes[N], mvars->chan->boxes[N], mvars->chan->patterns );
+ box_ent_t **mboxapp = &mvars->chanptr->boxes;
+ for (int mb = 0, sb = 0; ; ) {
+ char *fname = boxes[F] ? boxes[F][mb] : NULL;
+ char *nname = boxes[N] ? boxes[N][sb] : NULL;
+ if (!fname && !nname)
+ break;
+ box_ent_t *mbox = nfmalloc( sizeof(*mbox) );
+ int cmp;
+ if (!(cmp = !fname - !nname) && !(cmp = cmp_box_names( &fname, &nname ))) {
+ mbox->name = fname;
+ free( nname );
+ mbox->present[F] = mbox->present[N] = BOX_PRESENT;
+ mb++;
+ sb++;
+ } else if (cmp < 0) {
+ mbox->name = fname;
+ mbox->present[F] = BOX_PRESENT;
+ mbox->present[N] = (!mb && !strcmp( mbox->name, "INBOX" )) ? BOX_PRESENT : BOX_ABSENT;
+ mb++;
+ } else {
+ mbox->name = nname;
+ mbox->present[F] = (!sb && !strcmp( mbox->name, "INBOX" )) ? BOX_PRESENT : BOX_ABSENT;
+ mbox->present[N] = BOX_PRESENT;
+ sb++;
+ }
+ mbox->next = NULL;
+ *mboxapp = mbox;
+ mboxapp = &mbox->next;
+ boxes_total++;
+ }
+ free( boxes[F] );
+ free( boxes[N] );
+ if (!mvars->cvars->list)
+ stats();
+ }
+ mvars->boxptr = mvars->chanptr->boxes;
+
+ if (mvars->cvars->list && chans_total > 1)
+ printf( "%s:\n", mvars->chan->name );
+ mvars->box_done = 0;
+ do_sync_boxes( mvars );
+}
+
+static void
+do_sync_boxes( main_vars_t *mvars )
+{
+ mvars->box_cben = 0;
+ for (;;) {
+ if (mvars->chanptr->boxlist) {
+ box_ent_t *mbox = mvars->boxptr;
+ if (!mbox)
+ break;
+ mvars->boxptr = mbox->next;
+ mvars->box_done = 0;
+ if (mvars->chan->boxes[F] || mvars->chan->boxes[N]) {
+ const char *fpfx = nz( mvars->chan->boxes[F], "" );
+ const char *npfx = nz( mvars->chan->boxes[N], "" );
+ if (mvars->cvars->list) {
+ printf( "%s%s <=> %s%s\n", fpfx, mbox->name, npfx, mbox->name );
+ continue;
+ }
+ nfasprintf( &mvars->names[F], "%s%s", fpfx, mbox->name );
+ nfasprintf( &mvars->names[N], "%s%s", npfx, mbox->name );
+ sync_boxes( mvars->ctx, (const char * const *)mvars->names, mbox->present, mvars->chan, done_sync_dyn, mvars );
+ } else {
+ if (mvars->cvars->list) {
+ puts( mbox->name );
+ continue;
+ }
+ mvars->names[F] = mvars->names[N] = mbox->name;
+ sync_boxes( mvars->ctx, (const char * const *)mvars->names, mbox->present, mvars->chan, done_sync, mvars );
+ }
+ } else {
+ if (mvars->cvars->list) {
+ printf( "%s <=> %s\n", nz( mvars->chan->boxes[F], "INBOX" ), nz( mvars->chan->boxes[N], "INBOX" ) );
+ break;
+ }
+ if (mvars->box_done)
+ break;
+ int present[] = { BOX_POSSIBLE, BOX_POSSIBLE };
+ sync_boxes( mvars->ctx, mvars->chan->boxes, present, mvars->chan, done_sync, mvars );
+ }
+ if (!mvars->box_done) {
+ mvars->box_cben = 1;
+ return;
+ }
+ }
+ finalize_sync( mvars );
+}
+
+static void
+done_sync_dyn( int sts, void *aux )
+{
+ main_vars_t *mvars = (main_vars_t *)aux;
+
+ free( mvars->names[F] );
+ free( mvars->names[N] );
+ done_sync( sts, aux );
+}
+
+static void
+done_sync( int sts, void *aux )
+{
+ main_vars_t *mvars = (main_vars_t *)aux;
+
+ boxes_done++;
+ stats();
+ if (sts) {
+ mvars->cvars->ret = 1;
+ if (sts & (SYNC_BAD(F) | SYNC_BAD(N))) {
+ if (sts & SYNC_BAD(F))
+ mvars->state[F] = ST_CLOSED;
+ if (sts & SYNC_BAD(N))
+ mvars->state[N] = ST_CLOSED;
+ }
+ }
+ mvars->box_done = 1;
+ if (mvars->box_cben)
+ do_sync_boxes( mvars );
+}
+
+static void sync_finalized( void *aux );
+
+static void
+finalize_sync( main_vars_t *mvars )
+{
+ if (mvars->chanptr->boxlist) {
+ box_ent_t *mbox, *nmbox;
+ for (nmbox = mvars->chanptr->boxes; (mbox = nmbox); ) {
+ nmbox = mbox->next;
+ free( mbox->name );
+ free( mbox );
+ }
+ mvars->chanptr->boxes = NULL;
+ mvars->chanptr->boxlist = 0;
+ }
+
+ mvars->fnlz_cben = 0;
+ for (int t = 0; t < 2; t++) {
+ free_string_list( mvars->boxes[t] );
+ mvars->boxes[t] = NULL;
+
+ if (mvars->state[t] == ST_FRESH || mvars->state[t] == ST_OPEN) {
+ mvars->drv[t]->free_store( mvars->ctx[t] );
+ mvars->state[t] = ST_CLOSED;
+ } else if (mvars->state[t] == ST_CONNECTED) {
+ mvars->state[t] = ST_CANCELING;
+ mvars->drv[t]->cancel_cmds( mvars->ctx[t], sync_finalized, AUX );
+ }
+ }
+ if (mvars->state[F] != ST_CLOSED || mvars->state[N] != ST_CLOSED) {
+ mvars->fnlz_cben = 1;
+ return;
+ }
+ sync_next_chan( mvars );
+}
+
+static void
+sync_finalized( void *aux )
+{
+ MVARS(aux)
+
+ mvars->drv[t]->free_store( mvars->ctx[t] );
+ mvars->state[t] = ST_CLOSED;
+ if (mvars->state[t^1] != ST_CLOSED)
+ return;
+
+ if (mvars->fnlz_cben)
+ sync_next_chan( mvars );
+}