isync

mailbox synchronization program
git clone https://git.code.sf.net/p/isync/isync
Log | Files | Refs | README | LICENSE

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:
Msrc/Makefile.am | 5+++--
Msrc/driver.c | 7+++++++
Msrc/driver.h | 2++
Msrc/main.c | 703++++---------------------------------------------------------------------------
Asrc/main_p.h | 24++++++++++++++++++++++++
Asrc/main_sync.c | 692+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 ); +}