isync

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

commit 168e5f3282c9c1f8a2d703b9baf1a4c0dc8ea24c
parent bdcc285403a0e0f7c256367b5685082aa9bd027f
Author: Oswald Buddenhagen <ossi@users.sf.net>
Date:   Tue, 21 Mar 2006 20:03:21 +0000

make the driver model, sync_chans() and sync_boxes() fully async.
async drivers to follow ...

Diffstat:
Msrc/drv_imap.c | 183++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/drv_maildir.c | 198+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/isync.h | 55++++++++++++++++++++++++++++++++++++-------------------
Msrc/main.c | 232+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/sync.c | 1295+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/util.c | 12++----------
6 files changed, 1331 insertions(+), 644 deletions(-)

diff --git a/src/drv_imap.c b/src/drv_imap.c @@ -1199,8 +1199,9 @@ do_cram_auth( imap_store_t *ctx, struct imap_cmd *cmdp, const char *prompt ) } #endif -static store_t * -imap_open_store( store_conf_t *conf ) +static void +imap_open_store( store_conf_t *conf, + void (*cb)( store_t *srv, void *aux ), void *aux ) { imap_store_conf_t *cfg = (imap_store_conf_t *)conf; imap_server_conf_t *srvc = cfg->server; @@ -1367,12 +1368,12 @@ imap_open_store( store_conf_t *conf ) } #if HAVE_LIBSSL if (CAP(CRAM)) { - struct imap_cmd_cb cb; + struct imap_cmd_cb cbd; info( "Authenticating with CRAM-MD5\n" ); - memset( &cb, 0, sizeof(cb) ); - cb.cont = do_cram_auth; - if (imap_exec( ctx, &cb, "AUTHENTICATE CRAM-MD5" ) != RESP_OK) + memset( &cbd, 0, sizeof(cbd) ); + cbd.cont = do_cram_auth; + if (imap_exec( ctx, &cbd, "AUTHENTICATE CRAM-MD5" ) != RESP_OK) goto bail; } else if (srvc->require_cram) { error( "IMAP error: CRAM-MD5 authentication is not supported by server\n" ); @@ -1402,8 +1403,10 @@ imap_open_store( store_conf_t *conf ) else if (cfg->use_namespace && CAP(NAMESPACE)) { /* get NAMESPACE info */ if (!ctx->got_namespace) { - if (imap_exec( ctx, 0, "NAMESPACE" ) != RESP_OK) - goto bail; + if (imap_exec( ctx, 0, "NAMESPACE" ) != RESP_OK) { + cb( 0, aux ); + return; + } ctx->got_namespace = 1; } /* XXX for now assume personal namespace */ @@ -1413,11 +1416,13 @@ imap_open_store( store_conf_t *conf ) ctx->prefix = ctx->ns_personal->child->child->val; } ctx->trashnc = 1; - return &ctx->gen; + cb( &ctx->gen, aux ); + return; bail: imap_cancel_store( &ctx->gen ); - return 0; + cb( 0, aux ); + return; } static void @@ -1433,13 +1438,14 @@ imap_prepare_opts( store_t *gctx, int opts ) gctx->opts = opts; } -static int -imap_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs ) +static void +imap_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs, + void (*cb)( int sts, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; const char *prefix; int ret, i, j, bl; - struct imap_cmd_cb cb; + struct imap_cmd_cb cbd; char buf[1000]; @@ -1451,10 +1457,10 @@ imap_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs ) prefix = ctx->prefix; } - memset( &cb, 0, sizeof(cb) ); - cb.create = (gctx->opts & OPEN_CREATE) != 0; - cb.trycreate = 1; - if ((ret = imap_exec_b( ctx, &cb, "SELECT \"%s%s\"", prefix, gctx->name )) != DRV_OK) + memset( &cbd, 0, sizeof(cbd) ); + cbd.create = (gctx->opts & OPEN_CREATE) != 0; + cbd.trycreate = 1; + if ((ret = imap_exec_b( ctx, &cbd, "SELECT \"%s%s\"", prefix, gctx->name )) != DRV_OK) goto bail; if (gctx->count) { @@ -1489,19 +1495,20 @@ imap_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs ) bail: if (excs) free( excs ); - return ret; + cb( ret, aux ); } -static int -imap_fetch_msg( store_t *ctx, message_t *msg, msg_data_t *data ) +static void +imap_fetch_msg( store_t *ctx, message_t *msg, msg_data_t *data, + void (*cb)( int sts, void *aux ), void *aux ) { - struct imap_cmd_cb cb; + struct imap_cmd_cb cbd; - memset( &cb, 0, sizeof(cb) ); - cb.uid = msg->uid; - cb.ctx = data; - return imap_exec_m( (imap_store_t *)ctx, &cb, "UID FETCH %d (%sBODY.PEEK[])", - msg->uid, (msg->status & M_FLAGS) ? "" : "FLAGS " ); + memset( &cbd, 0, sizeof(cbd) ); + cbd.uid = msg->uid; + cbd.ctx = data; + cb( imap_exec_m( (imap_store_t *)ctx, &cbd, "UID FETCH %d (%sBODY.PEEK[])", + msg->uid, (msg->status & M_FLAGS) ? "" : "FLAGS " ), aux ); } static int @@ -1531,8 +1538,9 @@ imap_flags_helper( imap_store_t *ctx, int uid, char what, int flags) return issue_imap_cmd_w( ctx, 0, "UID STORE %d %cFLAGS.SILENT %s", uid, what, buf ) ? DRV_OK : DRV_STORE_BAD; } -static int -imap_set_flags( store_t *gctx, message_t *msg, int uid, int add, int del ) +static void +imap_set_flags( store_t *gctx, message_t *msg, int uid, int add, int del, + void (*cb)( int sts, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; int ret; @@ -1546,35 +1554,38 @@ imap_set_flags( store_t *gctx, message_t *msg, int uid, int add, int del ) } if ((!add || (ret = imap_flags_helper( ctx, uid, '+', add )) == DRV_OK) && (!del || (ret = imap_flags_helper( ctx, uid, '-', del )) == DRV_OK)) - return DRV_OK; - return ret; + ret = DRV_OK; + cb( ret, aux ); } -static int -imap_close( store_t *ctx ) +static void +imap_close( store_t *ctx, + void (*cb)( int sts, void *aux ), void *aux ) { - return imap_exec_b( (imap_store_t *)ctx, 0, "CLOSE" ); + cb( imap_exec_b( (imap_store_t *)ctx, 0, "CLOSE" ), aux ); } -static int -imap_trash_msg( store_t *gctx, message_t *msg ) +static void +imap_trash_msg( store_t *gctx, message_t *msg, + void (*cb)( int sts, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; - struct imap_cmd_cb cb; + struct imap_cmd_cb cbd; - memset( &cb, 0, sizeof(cb) ); - cb.create = 1; - return imap_exec_m( ctx, &cb, "UID COPY %d \"%s%s\"", - msg->uid, ctx->prefix, gctx->conf->trash ); + memset( &cbd, 0, sizeof(cbd) ); + cbd.create = 1; + cb( imap_exec_m( ctx, &cbd, "UID COPY %d \"%s%s\"", + msg->uid, ctx->prefix, gctx->conf->trash ), aux ); } -static int -imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) +static void +imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash, + void (*cb)( int sts, int uid, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; - struct imap_cmd_cb cb; + struct imap_cmd_cb cbd; const char *prefix, *box; - int ret, d; + int ret, d, uid; char flagstr[128]; d = 0; @@ -1584,71 +1595,82 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) } flagstr[d] = 0; - memset( &cb, 0, sizeof(cb) ); - cb.dlen = data->len; - cb.data = data->data; - if (!uid) { + memset( &cbd, 0, sizeof(cbd) ); + cbd.dlen = data->len; + cbd.data = data->data; + cbd.ctx = &uid; + uid = -2; + + if (to_trash) { box = gctx->conf->trash; prefix = ctx->prefix; - cb.create = 1; + cbd.create = 1; if (ctx->trashnc) ctx->caps = ctx->rcaps & ~(1 << LITERALPLUS); } else { box = gctx->name; prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix; - cb.create = (gctx->opts & OPEN_CREATE) != 0; + cbd.create = (gctx->opts & OPEN_CREATE) != 0; /*if (ctx->currentnc) ctx->caps = ctx->rcaps & ~(1 << LITERALPLUS);*/ - *uid = -2; } - cb.ctx = uid; - ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr ); + ret = imap_exec_m( ctx, &cbd, "APPEND \"%s%s\" %s", prefix, box, flagstr ); ctx->caps = ctx->rcaps; - if (ret != DRV_OK) - return ret; - if (!uid) + if (ret != DRV_OK) { + cb( ret, -1, aux ); + return; + } + if (to_trash) ctx->trashnc = 0; else { /*ctx->currentnc = 0;*/ - gctx->count++; } - return DRV_OK; + cb( DRV_OK, uid, aux ); } -static int -imap_find_msg( store_t *gctx, const char *tuid, int *uid ) +static void +imap_find_msg( store_t *gctx, const char *tuid, + void (*cb)( int sts, int uid, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; - struct imap_cmd_cb cb; - int ret; - - memset( &cb, 0, sizeof(cb) ); - cb.ctx = uid; - cb.uid = -1; /* we're looking for a UID */ - *uid = -1; /* in case we get no SEARCH response at all */ - if ((ret = imap_exec_m( ctx, &cb, "UID SEARCH HEADER X-TUID %." stringify(TUIDL) "s", tuid )) != DRV_OK) - return ret; - return *uid < 0 ? DRV_MSG_BAD : DRV_OK; + struct imap_cmd_cb cbd; + int ret, uid; + + memset( &cbd, 0, sizeof(cbd) ); + cbd.uid = -1; /* we're looking for a UID */ + cbd.ctx = &uid; + uid = -1; /* in case we get no SEARCH response at all */ + if ((ret = imap_exec_m( ctx, &cbd, "UID SEARCH HEADER X-TUID %." stringify(TUIDL) "s", tuid )) != DRV_OK) + cb( ret, -1, aux ); + else + cb( uid <= 0 ? DRV_MSG_BAD : DRV_OK, uid, aux ); } -static int -imap_list( store_t *gctx ) +static void +imap_list( store_t *gctx, + void (*cb)( int sts, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; int ret; if ((ret = imap_exec_b( ctx, 0, "LIST \"\" \"%s%%\"", ctx->prefix )) == DRV_OK) gctx->listed = 1; - return ret; + cb( ret, aux ); } -static int -imap_check( store_t *gctx ) +static void +imap_cancel( store_t *gctx, + void (*cb)( int sts, void *aux ), void *aux ) +{ + (void)gctx; + cb( DRV_OK, aux ); +} + +static void +imap_commit( store_t *gctx ) { - (void) gctx; - /* flush queue here */ - return DRV_OK; + (void)gctx; } imap_server_conf_t *servers, **serverapp = &servers; @@ -1797,6 +1819,7 @@ struct driver imap_driver = { imap_find_msg, imap_set_flags, imap_trash_msg, - imap_check, - imap_close + imap_close, + imap_cancel, + imap_commit, }; diff --git a/src/drv_maildir.c b/src/drv_maildir.c @@ -93,20 +93,22 @@ maildir_parse_flags( const char *base ) return flags; } -static store_t * -maildir_open_store( store_conf_t *conf ) +static void +maildir_open_store( store_conf_t *conf, + void (*cb)( store_t *ctx, void *aux ), void *aux ) { maildir_store_t *ctx; struct stat st; if (stat( conf->path, &st ) || !S_ISDIR(st.st_mode)) { error( "Maildir error: cannot open store %s\n", conf->path ); - return 0; + cb( 0, aux ); + return; } ctx = nfcalloc( sizeof(*ctx) ); ctx->gen.conf = conf; ctx->uvfd = -1; - return &ctx->gen; + cb( &ctx->gen, aux ); } static void @@ -159,15 +161,17 @@ maildir_cleanup_drv( void ) { } -static int -maildir_list( store_t *gctx ) +static void +maildir_list( store_t *gctx, + void (*cb)( int sts, void *aux ), void *aux ) { DIR *dir; struct dirent *de; if (!(dir = opendir( gctx->conf->path ))) { error( "%s: %s\n", gctx->conf->path, strerror(errno) ); - return DRV_STORE_BAD; + cb( DRV_STORE_BAD, aux ); + return; } while ((de = readdir( dir ))) { struct stat st; @@ -183,7 +187,7 @@ maildir_list( store_t *gctx ) closedir (dir); gctx->listed = 1; - return DRV_OK; + cb( DRV_OK, aux ); } static const char *subdirs[] = { "cur", "new", "tmp" }; @@ -760,8 +764,9 @@ maildir_prepare_opts( store_t *gctx, int opts ) gctx->opts = opts; } -static int -maildir_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs ) +static void +maildir_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs, + void (*cb)( int sts, void *aux ), void *aux ) { maildir_store_t *ctx = (maildir_store_t *)gctx; message_t **msgapp; @@ -777,14 +782,17 @@ maildir_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs ) ctx->excs = nfrealloc( excs, nexcs * sizeof(int) ); ctx->nexcs = nexcs; - if (maildir_validate( gctx->path, "", ctx->gen.opts & OPEN_CREATE ) != DRV_OK) - return DRV_BOX_BAD; + if (maildir_validate( gctx->path, "", ctx->gen.opts & OPEN_CREATE ) != DRV_OK) { + cb( DRV_BOX_BAD, aux ); + return; + } nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", gctx->path ); #ifndef USE_DB if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) < 0) { perror( uvpath ); - return DRV_BOX_BAD; + cb( DRV_BOX_BAD, aux ); + return; } #else if ((ctx->uvfd = open( uvpath, O_RDWR, 0600 )) < 0) { @@ -799,7 +807,8 @@ maildir_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs ) goto fnok; } perror( uvpath ); - return DRV_BOX_BAD; + cb( DRV_BOX_BAD, aux ); + return; } dbok: #if SEEK_SET != 0 @@ -811,7 +820,8 @@ maildir_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs ) bork: close( ctx->uvfd ); ctx->uvfd = -1; - return DRV_BOX_BAD; + cb( DRV_BOX_BAD, aux ); + return; } if (db_create( &ctx->db, 0, 0 )) { fputs( "Maildir error: db_create() failed\n", stderr ); @@ -841,14 +851,16 @@ maildir_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs ) fnok: #endif /* USE_DB */ - if (maildir_scan( ctx, &msglist ) != DRV_OK) - return DRV_BOX_BAD; + if (maildir_scan( ctx, &msglist ) != DRV_OK) { + cb( DRV_BOX_BAD, aux ); + return; + } msgapp = &ctx->gen.msgs; for (i = 0; i < msglist.nents; i++) maildir_app_msg( ctx, &msgapp, msglist.ents + i ); maildir_free_scan( &msglist ); - return DRV_OK; + cb( DRV_OK, aux ); } static int @@ -916,8 +928,9 @@ maildir_again( maildir_store_t *ctx, maildir_message_t *msg, const char *fn ) return (msg->gen.status & M_DEAD) ? DRV_MSG_BAD : DRV_OK; } -static int -maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data ) +static void +maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data, + void (*cb)( int sts, void *aux ), void *aux ) { maildir_store_t *ctx = (maildir_store_t *)gctx; maildir_message_t *msg = (maildir_message_t *)gmsg; @@ -929,8 +942,10 @@ maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data ) nfsnprintf( buf, sizeof(buf), "%s/%s/%s", gctx->path, subdirs[gmsg->status & M_RECENT], msg->base ); if ((fd = open( buf, O_RDONLY )) >= 0) break; - if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK) - return ret; + if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK) { + cb( ret, aux ); + return; + } } fstat( fd, &st ); data->len = st.st_size; @@ -938,12 +953,13 @@ maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data ) if (read( fd, data->data, data->len ) != data->len) { perror( buf ); close( fd ); - return DRV_MSG_BAD; + cb( DRV_MSG_BAD, aux ); + return; } close( fd ); if (!(gmsg->status & M_FLAGS)) data->flags = maildir_parse_flags( msg->base ); - return DRV_OK; + cb( DRV_OK, aux ); } static int @@ -961,30 +977,34 @@ maildir_make_flags( int flags, char *buf ) return d; } -static int -maildir_store_msg( store_t *gctx, msg_data_t *data, int *uid ) +static void +maildir_store_msg( store_t *gctx, msg_data_t *data, int to_trash, + void (*cb)( int sts, int uid, void *aux ), void *aux ) { maildir_store_t *ctx = (maildir_store_t *)gctx; const char *prefix, *box; - int ret, fd, bl; + int ret, fd, bl, uid; char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX], fbuf[NUM_FLAGS + 3], base[128]; bl = nfsnprintf( base, sizeof(base), "%ld.%d_%d.%s", time( 0 ), Pid, ++MaildirCount, Hostname ); - if (uid) { + if (!to_trash) { #ifdef USE_DB if (ctx->db) { - if ((ret = maildir_set_uid( ctx, base, uid )) != DRV_OK) { + if ((ret = maildir_set_uid( ctx, base, &uid )) != DRV_OK) { free( data->data ); - return ret; + cb( ret, 0, aux ); + return; } } else #endif /* USE_DB */ { if ((ret = maildir_uidval_lock( ctx )) != DRV_OK || - (ret = maildir_obtain_uid( ctx, uid )) != DRV_OK) - return ret; + (ret = maildir_obtain_uid( ctx, &uid )) != DRV_OK) { + cb( ret, 0, aux ); + return; + } maildir_uidval_unlock( ctx ); - nfsnprintf( base + bl, sizeof(base) - bl, ",U=%d", *uid ); + nfsnprintf( base + bl, sizeof(base) - bl, ",U=%d", uid ); } prefix = gctx->path; box = ""; @@ -999,16 +1019,19 @@ maildir_store_msg( store_t *gctx, msg_data_t *data, int *uid ) if (errno != ENOENT) { perror( buf ); free( data->data ); - return DRV_BOX_BAD; + cb( DRV_BOX_BAD, 0, aux ); + return; } if ((ret = maildir_validate( gctx->conf->path, gctx->conf->trash, gctx->opts & OPEN_CREATE )) != DRV_OK) { free( data->data ); - return ret; + cb( ret, 0, aux ); + return; } if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) { perror( buf ); free( data->data ); - return DRV_BOX_BAD; + cb( DRV_BOX_BAD, 0, aux ); + return; } } ret = write( fd, data->data, data->len ); @@ -1019,35 +1042,37 @@ maildir_store_msg( store_t *gctx, msg_data_t *data, int *uid ) else error( "Maildir error: %s: partial write\n", buf ); close( fd ); - return DRV_BOX_BAD; + cb( DRV_BOX_BAD, 0, aux ); + return; } close( fd ); nfsnprintf( nbuf, sizeof(nbuf), "%s%s/%s/%s%s", prefix, box, subdirs[!(data->flags & F_SEEN)], base, fbuf ); if (rename( buf, nbuf )) { perror( nbuf ); - return DRV_BOX_BAD; + cb( DRV_BOX_BAD, 0, aux ); + return; } - if (uid) - gctx->count++; - return DRV_OK; + cb( DRV_OK, uid, aux ); } -static int -maildir_find_msg( store_t *gctx, const char *tuid, int *uid ) +static void +maildir_find_msg( store_t *gctx, const char *tuid, + void (*cb)( int sts, int uid, void *aux ), void *aux ) { message_t *msg; /* using a hash table might turn out to be more appropriate ... */ for (msg = gctx->msgs; msg; msg = msg->next) if (!(msg->status & M_DEAD) && !memcmp( ((maildir_message_t *)msg)->tuid, tuid, TUIDL )) { - *uid = msg->uid; - return DRV_OK; + cb( DRV_OK, msg->uid, aux ); + return; } - return DRV_MSG_BAD; + cb( DRV_MSG_BAD, -1, aux ); } -static int -maildir_set_flags( store_t *gctx, message_t *gmsg, int uid, int add, int del ) +static void +maildir_set_flags( store_t *gctx, message_t *gmsg, int uid, int add, int del, + void (*cb)( int sts, void *aux ), void *aux ) { maildir_store_t *ctx = (maildir_store_t *)gctx; maildir_message_t *msg = (maildir_message_t *)gmsg; @@ -1089,8 +1114,10 @@ maildir_set_flags( store_t *gctx, message_t *gmsg, int uid, int add, int del ) } if (!rename( buf, nbuf )) break; - if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK) - return ret; + if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK) { + cb( ret, aux ); + return; + } } free( msg->base ); msg->base = nfmalloc( tl + 1 ); @@ -1099,7 +1126,7 @@ maildir_set_flags( store_t *gctx, message_t *gmsg, int uid, int add, int del ) msg->gen.flags &= ~del; gmsg->status &= ~M_RECENT; - return DRV_OK; + cb( DRV_OK, aux ); } #ifdef USE_DB @@ -1119,8 +1146,9 @@ maildir_purge_msg( maildir_store_t *ctx, const char *name ) } #endif /* USE_DB */ -static int -maildir_trash_msg( store_t *gctx, message_t *gmsg ) +static void +maildir_trash_msg( store_t *gctx, message_t *gmsg, + void (*cb)( int sts, void *aux ), void *aux ) { maildir_store_t *ctx = (maildir_store_t *)gctx; maildir_message_t *msg = (maildir_message_t *)gmsg; @@ -1137,30 +1165,38 @@ maildir_trash_msg( store_t *gctx, message_t *gmsg ) if (!rename( buf, nbuf )) break; if (!stat( buf, &st )) { - if ((ret = maildir_validate( gctx->conf->path, gctx->conf->trash, 1 )) != DRV_OK) - return ret; + if ((ret = maildir_validate( gctx->conf->path, gctx->conf->trash, 1 )) != DRV_OK) { + cb( ret, aux ); + return; + } if (!rename( buf, nbuf )) break; if (errno != ENOENT) { perror( nbuf ); - return DRV_BOX_BAD; + cb( DRV_BOX_BAD, aux ); + return; } } - if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK) - return ret; + if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK) { + cb( ret, aux ); + return; + } } gmsg->status |= M_DEAD; gctx->count--; #ifdef USE_DB - if (ctx->db) - return maildir_purge_msg( ctx, msg->base ); + if (ctx->db) { + cb( maildir_purge_msg( ctx, msg->base ), aux ); + return; + } #endif /* USE_DB */ - return DRV_OK; + cb( DRV_OK, aux ); } -static int -maildir_close( store_t *gctx ) +static void +maildir_close( store_t *gctx, + void (*cb)( int sts, void *aux ), void *aux ) { #ifdef USE_DB maildir_store_t *ctx = (maildir_store_t *)gctx; @@ -1184,23 +1220,36 @@ maildir_close( store_t *gctx ) msg->status |= M_DEAD; gctx->count--; #ifdef USE_DB - if (ctx->db && (ret = maildir_purge_msg( ctx, ((maildir_message_t *)msg)->base )) != DRV_OK) - return ret; + if (ctx->db && (ret = maildir_purge_msg( ctx, ((maildir_message_t *)msg)->base )) != DRV_OK) { + cb( ret, aux ); + return; + } #endif /* USE_DB */ } } - if (!retry) - return DRV_OK; - if ((ret = maildir_rescan( (maildir_store_t *)gctx )) != DRV_OK) - return ret; + if (!retry) { + cb( DRV_OK, aux ); + return; + } + if ((ret = maildir_rescan( (maildir_store_t *)gctx )) != DRV_OK) { + cb( ret, aux ); + return; + } } } -static int -maildir_check( store_t *gctx ) +static void +maildir_cancel( store_t *gctx, + void (*cb)( int sts, void *aux ), void *aux ) +{ + (void)gctx; + cb( DRV_OK, aux ); +} + +static void +maildir_commit( store_t *gctx ) { (void) gctx; - return DRV_OK; } static int @@ -1248,6 +1297,7 @@ struct driver maildir_driver = { maildir_find_msg, maildir_set_flags, maildir_trash_msg, - maildir_check, - maildir_close + maildir_close, + maildir_cancel, + maildir_commit, }; diff --git a/src/isync.h b/src/isync.h @@ -166,9 +166,13 @@ typedef struct { } msg_data_t; #define DRV_OK 0 -#define DRV_MSG_BAD -1 -#define DRV_BOX_BAD -2 -#define DRV_STORE_BAD -3 +#define DRV_MSG_BAD 1 +#define DRV_BOX_BAD 2 +#define DRV_STORE_BAD 3 +#define DRV_SERVER_BAD 4 +#define DRV_CANCELED 5 + +/* All memory belongs to the driver's user. */ #define DRV_CRLF 1 @@ -178,21 +182,32 @@ struct driver { int flags; int (*parse_store)( conffile_t *cfg, store_conf_t **storep, int *err ); void (*cleanup)( void ); - store_t *(*open_store)( store_conf_t *conf ); + void (*open_store)( store_conf_t *conf, + void (*cb)( store_t *ctx, void *aux ), void *aux ); void (*disown_store)( store_t *ctx ); store_t *(*own_store)( store_conf_t *conf ); void (*cancel_store)( store_t *ctx ); - int (*list)( store_t *ctx ); + void (*list)( store_t *ctx, + void (*cb)( int sts, void *aux ), void *aux ); void (*prepare_paths)( store_t *ctx ); void (*prepare_opts)( store_t *ctx, int opts ); - int (*select)( store_t *ctx, int minuid, int maxuid, int *excs, int nexcs ); - int (*fetch_msg)( store_t *ctx, message_t *msg, msg_data_t *data ); - int (*store_msg)( store_t *ctx, msg_data_t *data, int *uid ); /* if uid is null, store to trash */ - int (*find_msg)( store_t *ctx, const char *tuid, int *uid ); - int (*set_flags)( store_t *ctx, message_t *msg, int uid, int add, int del ); /* msg can be null, therefore uid as a fallback */ - int (*trash_msg)( store_t *ctx, message_t *msg ); /* This may expunge the original message immediately, but it needn't to */ - int (*check)( store_t *ctx ); /* IMAP-style: flush */ - int (*close)( store_t *ctx ); /* IMAP-style: expunge inclusive */ + void (*select)( store_t *ctx, int minuid, int maxuid, int *excs, int nexcs, + void (*cb)( int sts, void *aux ), void *aux ); + void (*fetch_msg)( store_t *ctx, message_t *msg, msg_data_t *data, + void (*cb)( int sts, void *aux ), void *aux ); + void (*store_msg)( store_t *ctx, msg_data_t *data, int to_trash, + void (*cb)( int sts, int uid, void *aux ), void *aux ); + void (*find_msg)( store_t *ctx, const char *tuid, + void (*cb)( int sts, int uid, void *aux ), void *aux ); + void (*set_flags)( store_t *ctx, message_t *msg, int uid, int add, int del, /* msg can be null, therefore uid as a fallback */ + void (*cb)( int sts, void *aux ), void *aux ); + void (*trash_msg)( store_t *ctx, message_t *msg, /* This may expunge the original message immediately, but it needn't to */ + void (*cb)( int sts, void *aux ), void *aux ); + void (*close)( store_t *ctx, /* IMAP-style: expunge inclusive */ + void (*cb)( int sts, void *aux ), void *aux ); + void (*cancel)( store_t *ctx, /* only not yet sent commands */ + void (*cb)( int sts, void *aux ), void *aux ); + void (*commit)( store_t *ctx ); }; @@ -217,7 +232,6 @@ void debug( const char *, ... ); void debugn( const char *, ... ); void info( const char *, ... ); void infon( const char *, ... ); -void infoc( char ); void warn( const char *, ... ); void error( const char *, ... ); @@ -248,12 +262,15 @@ unsigned char arc4_getbyte( void ); extern const char *str_ms[2], *str_hl[2]; -#define SYNC_OK 0 -#define SYNC_FAIL 1 -#define SYNC_BAD(ms) (2+(ms)) -#define SYNC_NOGOOD 4 /* internal */ +#define SYNC_OK 0 /* assumed to be 0 */ +#define SYNC_FAIL 1 +#define SYNC_BAD(ms) (2<<(ms)) +#define SYNC_NOGOOD 8 /* internal */ +#define SYNC_CANCELED 16 /* internal */ -int sync_boxes( store_t *ctx[], const char *names[], channel_conf_t * ); +/* All passed pointers must stay alive until cb is called. */ +void sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan, + void (*cb)( int sts, void *aux ), void *aux ); /* config.c */ diff --git a/src/main.c b/src/main.c @@ -24,6 +24,7 @@ #include "isync.h" #include <stdlib.h> +#include <stddef.h> #include <unistd.h> #include <string.h> #include <fcntl.h> @@ -198,7 +199,16 @@ typedef struct { unsigned done:1, skip:1, cben:1; } main_vars_t; -static void sync_chans( main_vars_t *mvars ); +#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 ) @@ -460,19 +470,36 @@ main( int argc, char **argv ) break; } mvars->argv = argv; - sync_chans( mvars ); + mvars->cben = 1; + sync_chans( mvars, E_START ); return mvars->ret; } +#define ST_FRESH 0 +#define ST_OPEN 1 +#define ST_CLOSED 2 + +static void store_opened( store_t *ctx, void *aux ); +static void store_listed( int sts, void *aux ); +static void done_sync_dyn( int sts, void *aux ); +static void done_sync( int sts, void *aux ); + static void -sync_chans( main_vars_t *mvars ) +sync_chans( main_vars_t *mvars, int ent ) { group_conf_t *group; channel_conf_t *chan; + store_t *store; string_list_t *mbox, *sbox, **mboxp, **sboxp; char *channame; int t; + if (!mvars->cben) + return; + switch (ent) { + case E_OPEN: goto opened; + case E_SYNC: goto syncone; + } for (;;) { mvars->boxlist = 0; if (!mvars->all) { @@ -503,36 +530,32 @@ sync_chans( main_vars_t *mvars ) merge_actions( mvars->chan, mvars->ops, XOP_HAVE_CREATE, OP_CREATE, 0 ); merge_actions( mvars->chan, mvars->ops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 ); + mvars->state[M] = mvars->state[S] = ST_FRESH; info( "Channel %s\n", mvars->chan->name ); mvars->boxes[M] = mvars->boxes[S] = mvars->cboxes = 0; + mvars->skip = mvars->cben = 0; for (t = 0; t < 2; t++) { mvars->drv[t] = mvars->chan->stores[t]->driver; - mvars->ctx[t] = mvars->drv[t]->own_store( mvars->chan->stores[t] ); + if ((store = mvars->drv[t]->own_store( mvars->chan->stores[t] ))) + store_opened( store, AUX ); } - for (t = 0; t < 2; t++) - if (!mvars->ctx[t]) { + for (t = 0; t < 2 && !mvars->skip; t++) + if (mvars->state[t] == ST_FRESH) { info( "Opening %s %s...\n", str_ms[t], mvars->chan->stores[t]->name ); - if (!(mvars->ctx[t] = mvars->drv[t]->open_store( mvars->chan->stores[t] ))) { - mvars->ret = 1; - goto next; - } + mvars->drv[t]->open_store( mvars->chan->stores[t], store_opened, AUX ); } + mvars->cben = 1; + opened: + if (mvars->skip) + goto next; + if (mvars->state[M] != ST_OPEN || mvars->state[S] != ST_OPEN) + return; + if (mvars->boxlist) mvars->boxp = mvars->boxlist; else if (mvars->chan->patterns) { - for (t = 0; t < 2; t++) { - if (!mvars->ctx[t]->listed) { - if (mvars->drv[t]->list( mvars->ctx[t] ) != DRV_OK) { - screwt: - mvars->drv[t]->cancel_store( mvars->ctx[t] ); - mvars->ctx[t] = 0; - mvars->ret = 1; - goto next; - } else if (mvars->ctx[t]->conf->map_inbox) - add_string_list( &mvars->ctx[t]->boxes, mvars->ctx[t]->conf->map_inbox ); - } - mvars->boxes[t] = filter_boxes( mvars->ctx[t]->boxes, mvars->chan->patterns ); - } + mvars->boxes[M] = filter_boxes( mvars->ctx[M]->boxes, mvars->chan->patterns ); + mvars->boxes[S] = filter_boxes( mvars->ctx[S]->boxes, mvars->chan->patterns ); for (mboxp = &mvars->boxes[M]; (mbox = *mboxp); ) { for (sboxp = &mvars->boxes[S]; (sbox = *sboxp); sboxp = &sbox->next) if (!strcmp( sbox->string, mbox->string )) { @@ -550,60 +573,70 @@ sync_chans( main_vars_t *mvars ) if (mvars->list && mvars->multiple) printf( "%s:\n", mvars->chan->name ); + syncml: + mvars->done = mvars->cben = 0; + syncmlx: if (mvars->boxlist) { - while ((mvars->names[S] = strsep( &mvars->boxp, ",\n" ))) { - if (mvars->list) - puts( mvars->names[S] ); - else { + if ((mvars->names[S] = strsep( &mvars->boxp, ",\n" ))) { + if (!mvars->list) { mvars->names[M] = mvars->names[S]; - switch (sync_boxes( mvars->ctx, mvars->names, mvars->chan )) { - case SYNC_BAD(M): t = M; goto screwt; - case SYNC_BAD(S): t = S; goto screwt; - case SYNC_FAIL: mvars->ret = 1; - } + sync_boxes( mvars->ctx, mvars->names, mvars->chan, done_sync, mvars ); + goto syncw; } + puts( mvars->names[S] ); + goto syncmlx; } } else if (mvars->chan->patterns) { - for (mbox = mvars->cboxes; mbox; mbox = mbox->next) - if (mvars->list) - puts( mbox->string ); - else { + if ((mbox = mvars->cboxes)) { + mvars->cboxes = mbox->next; + if (!mvars->list) { mvars->names[M] = mvars->names[S] = mbox->string; - switch (sync_boxes( mvars->ctx, mvars->names, mvars->chan )) { - case SYNC_BAD(M): t = M; goto screwt; - case SYNC_BAD(S): t = S; goto screwt; - case SYNC_FAIL: mvars->ret = 1; - } + sync_boxes( mvars->ctx, mvars->names, mvars->chan, done_sync_dyn, mvars ); + goto syncw; } + puts( mbox->string ); + free( mbox ); + goto syncmlx; + } for (t = 0; t < 2; t++) - if ((mvars->chan->ops[1-t] & OP_MASK_TYPE) && (mvars->chan->ops[1-t] & OP_CREATE)) { - for (mbox = mvars->boxes[t]; mbox; mbox = mbox->next) - if (mvars->list) - puts( mbox->string ); - else { + if ((mbox = mvars->boxes[t])) { + mvars->boxes[t] = mbox->next; + if ((mvars->chan->ops[1-t] & OP_MASK_TYPE) && (mvars->chan->ops[1-t] & OP_CREATE)) { + if (!mvars->list) { mvars->names[M] = mvars->names[S] = mbox->string; - switch (sync_boxes( mvars->ctx, mvars->names, mvars->chan )) { - case SYNC_BAD(M): t = M; goto screwt; - case SYNC_BAD(S): t = S; goto screwt; - case SYNC_FAIL: mvars->ret = 1; - } + sync_boxes( mvars->ctx, mvars->names, mvars->chan, done_sync_dyn, mvars ); + goto syncw; } + puts( mbox->string ); + } + free( mbox ); + goto syncmlx; } - } else - if (mvars->list) + } else { + if (!mvars->list) { + sync_boxes( mvars->ctx, mvars->chan->boxes, 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", mvars->chan->boxes[M], mvars->chan->boxes[S] ); - else - switch (sync_boxes( mvars->ctx, mvars->chan->boxes, mvars->chan )) { - case SYNC_BAD(M): t = M; goto screwt; - case SYNC_BAD(S): t = S; goto screwt; - case SYNC_FAIL: mvars->ret = 1; - } + } next: - if (mvars->ctx[M]) - mvars->drv[M]->disown_store( mvars->ctx[M] ); - if (mvars->ctx[S]) - mvars->drv[S]->disown_store( mvars->ctx[S] ); + for (t = 0; t < 2; t++) + if (mvars->state[t] == ST_OPEN) { + mvars->drv[t]->disown_store( mvars->ctx[t] ); + mvars->state[t] = ST_CLOSED; + } + if (mvars->state[M] != ST_CLOSED || mvars->state[S] != ST_CLOSED) { + mvars->skip = mvars->cben = 1; + return; + } free_string_list( mvars->cboxes ); free_string_list( mvars->boxes[M] ); free_string_list( mvars->boxes[S] ); @@ -621,3 +654,76 @@ sync_chans( main_vars_t *mvars ) for (t = 0; t < N_DRIVERS; t++) drivers[t]->cleanup(); } + +static void +store_opened( store_t *ctx, void *aux ) +{ + MVARS(aux) + + if (!ctx) { + mvars->state[t] = ST_CLOSED; + mvars->ret = mvars->skip = 1; + return; + } + mvars->ctx[t] = ctx; + if (mvars->skip) { + mvars->state[t] = ST_OPEN; + sync_chans( mvars, E_OPEN ); + return; + } + if (!mvars->boxlist && mvars->chan->patterns && !ctx->listed) + mvars->drv[t]->list( ctx, store_listed, AUX ); + else { + mvars->state[t] = ST_OPEN; + sync_chans( mvars, E_OPEN ); + } +} + +static void +store_listed( int sts, void *aux ) +{ + MVARS(aux) + + mvars->state[t] = ST_OPEN; + switch (sts) { + case DRV_OK: + if (mvars->ctx[t]->conf->map_inbox) + add_string_list( &mvars->ctx[t]->boxes, mvars->ctx[t]->conf->map_inbox ); + break; + case DRV_STORE_BAD: + mvars->drv[t]->cancel_store( mvars->ctx[t] ); + mvars->state[t] = ST_CLOSED; + default: + mvars->ret = mvars->skip = 1; + break; + } + sync_chans( mvars, E_OPEN ); +} + +static void +done_sync_dyn( int sts, void *aux ) +{ + main_vars_t *mvars = (main_vars_t *)aux; + + free( ((char *)mvars->names[S]) - offsetof(string_list_t, string) ); + done_sync( sts, aux ); +} + +static void +done_sync( int sts, void *aux ) +{ + main_vars_t *mvars = (main_vars_t *)aux; + + mvars->done = 1; + if (sts) { + mvars->ret = 1; + if (sts & (SYNC_BAD(M) | SYNC_BAD(S))) { + mvars->skip = 1; + if (sts & SYNC_BAD(M)) + mvars->state[M] = ST_CLOSED; + if (sts & SYNC_BAD(S)) + mvars->state[S] = ST_CLOSED; + } + } + sync_chans( mvars, E_SYNC ); +} diff --git a/src/sync.c b/src/sync.c @@ -26,6 +26,7 @@ #include <stdio.h> #include <limits.h> #include <stdlib.h> +#include <stddef.h> #include <unistd.h> #include <time.h> #include <fcntl.h> @@ -87,6 +88,7 @@ make_flags( int flags, char *buf ) return d; } + #define S_DEAD (1<<0) #define S_DONE (1<<1) #define S_DEL(ms) (1<<(2+(ms))) @@ -106,225 +108,6 @@ typedef struct sync_rec { char tuid[TUIDL]; } sync_rec_t; -static int -select_box( sync_rec_t *srecs, store_t *ctx[], int maxuid[], int uidval[], int t, int minwuid, int *mexcs, int nmexcs, FILE *jfp ) -{ - sync_rec_t *srec, *nsrec = 0; - message_t *msg; - const char *diag; - int uid, maxwuid; - char fbuf[16]; /* enlarge when support for keywords is added */ - - if (ctx[t]->opts & OPEN_NEW) { - if (minwuid > maxuid[t] + 1) - minwuid = maxuid[t] + 1; - maxwuid = INT_MAX; - } else if (ctx[t]->opts & OPEN_OLD) { - maxwuid = 0; - for (srec = srecs; srec; srec = srec->next) - if (!(srec->status & S_DEAD) && srec->uid[t] > maxwuid) - maxwuid = srec->uid[t]; - } else - maxwuid = 0; - infon( "Selecting %s %s... ", str_ms[t], ctx[t]->name ); - debug( maxwuid == INT_MAX ? "selecting %s [%d,inf]\n" : "selecting %s [%d,%d]\n", str_ms[t], minwuid, maxwuid ); - switch (ctx[t]->conf->driver->select( ctx[t], minwuid, maxwuid, mexcs, nmexcs )) { - case DRV_STORE_BAD: return SYNC_BAD(t); - case DRV_BOX_BAD: return SYNC_FAIL; - } - if (uidval[t] && uidval[t] != ctx[t]->uidvalidity) { - error( "Error: UIDVALIDITY of %s changed (got %d, expected %d)\n", str_ms[t], ctx[t]->uidvalidity, uidval[t] ); - return SYNC_FAIL; - } - info( "%d messages, %d recent\n", ctx[M]->count, ctx[M]->recent ); - - if (jfp) { - /* - * Alternatively, the TUIDs could be fetched into the messages and - * looked up here. This would make the search faster (probably) and - * save roundtrips. On the downside, quite some additional data would - * have to be fetched for every message and the IMAP driver would be - * more complicated. This is a corner case anyway, so why bother. - */ - debug( "finding previously copied messages\n" ); - for (srec = srecs; srec; srec = srec->next) { - if (srec->status & S_DEAD) - continue; - if (srec->uid[t] == -2 && srec->tuid[0]) { - debug( " pair(%d,%d): lookup %s, TUID %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], str_ms[t], srec->tuid ); - switch (ctx[t]->conf->driver->find_msg( ctx[t], srec->tuid, &uid )) { - case DRV_STORE_BAD: return SYNC_BAD(t); - case DRV_OK: - debug( " -> new UID %d\n", uid ); - Fprintf( jfp, "%c %d %d %d\n", "<>"[t], srec->uid[M], srec->uid[S], uid ); - srec->uid[t] = uid; - srec->tuid[0] = 0; - break; - default: - debug( " -> TUID lost\n" ); - Fprintf( jfp, "& %d %d\n", srec->uid[M], srec->uid[S] ); - srec->flags = 0; - srec->tuid[0] = 0; - break; - } - } - } - } - - /* - * Mapping msg -> srec (this variant) is dog slow for new messages. - * Mapping srec -> msg is dog slow for deleted messages. - * One solution would be using binary search on an index array. - * msgs are already sorted by UID, srecs would have to be sorted by uid[t]. - */ - debug( "matching messages against sync records\n" ); - for (msg = ctx[t]->msgs; msg; msg = msg->next) { - uid = msg->uid; - if (DFlags & DEBUG) { - make_flags( msg->flags, fbuf ); - printf( ctx[t]->opts & OPEN_SIZE ? " message %5d, %-4s, %6d: " : " message %5d, %-4s: ", uid, fbuf, msg->size ); - } - for (srec = nsrec; srec; srec = srec->next) { - if (srec->status & S_DEAD) - continue; - if (srec->uid[t] == uid) { - diag = srec == nsrec ? "adjacently" : "after gap"; - goto found; - } - } - for (srec = srecs; srec != nsrec; srec = srec->next) { - if (srec->status & S_DEAD) - continue; - if (srec->uid[t] == uid) { - diag = "after reset"; - goto found; - } - } - msg->srec = 0; - debug( "new\n" ); - continue; - found: - msg->srec = srec; - srec->msg[t] = msg; - nsrec = srec->next; - debug( "pairs %5d %s\n", srec->uid[1-t], diag ); - } - - return SYNC_OK; -} - -static int -copy_msg( store_t *ctx[], int t, message_t *tmsg, const char *tuid, int *uid ) -{ - msg_data_t msgdata; - char *fmap, *buf; - int i, len, extra, cra, crd, scr, tcr; - int start, sbreak = 0, ebreak = 0; - char c; - - msgdata.flags = tmsg->flags; - switch (ctx[1-t]->conf->driver->fetch_msg( ctx[1-t], tmsg, &msgdata )) { - case DRV_STORE_BAD: return SYNC_BAD(1-t); - case DRV_BOX_BAD: return SYNC_FAIL; - case DRV_MSG_BAD: return SYNC_NOGOOD; - } - tmsg->flags = msgdata.flags; - - scr = (ctx[1-t]->conf->driver->flags / DRV_CRLF) & 1; - tcr = (ctx[t]->conf->driver->flags / DRV_CRLF) & 1; - if (tuid || scr != tcr) { - fmap = msgdata.data; - len = msgdata.len; - cra = crd = 0; - if (scr > tcr) - crd = -1; - else if (scr < tcr) - cra = 1; - extra = 0, i = 0; - if (tuid) { - extra += 8 + TUIDL + 1 + tcr; - nloop: - start = i; - while (i < len) { - c = fmap[i++]; - if (c == '\r') - extra += crd; - else if (c == '\n') { - extra += cra; - if (i - 1 - scr == start) { - sbreak = ebreak = i - 1 - scr; - goto oke; - } - if (!memcmp( fmap + start, "X-TUID: ", 8 )) { - extra -= (ebreak = i) - (sbreak = start); - goto oke; - } - goto nloop; - } - } - /* invalid message */ - free( fmap ); - return SYNC_NOGOOD; - } - oke: - if (cra || crd) - for (; i < len; i++) { - c = fmap[i]; - if (c == '\r') - extra += crd; - else if (c == '\n') - extra += cra; - } - - msgdata.len = len + extra; - buf = msgdata.data = nfmalloc( msgdata.len ); - i = 0; - if (tuid) { - if (cra) { - for (; i < sbreak; i++) { - if (fmap[i] == '\n') - *buf++ = '\r'; - *buf++ = fmap[i]; - } - } else if (crd) { - for (; i < sbreak; i++) - if (fmap[i] != '\r') - *buf++ = fmap[i]; - } else { - memcpy( buf, fmap, sbreak ); - buf += sbreak; - } - memcpy( buf, "X-TUID: ", 8 ); - buf += 8; - memcpy( buf, tuid, TUIDL ); - buf += TUIDL; - if (tcr) - *buf++ = '\r'; - *buf++ = '\n'; - i = ebreak; - } - if (cra) { - for (; i < len; i++) { - if (fmap[i] == '\n') - *buf++ = '\r'; - *buf++ = fmap[i]; - } - } else if (crd) { - for (; i < len; i++) - if (fmap[i] != '\r') - *buf++ = fmap[i]; - } else - memcpy( buf, fmap + i, len - i ); - - free( fmap ); - } - - switch (ctx[t]->conf->driver->store_msg( ctx[t], &msgdata, uid )) { - case DRV_STORE_BAD: return SYNC_BAD(t); - case DRV_OK: return SYNC_OK; - default: return SYNC_FAIL; - } -} /* cases: a) both non-null @@ -354,6 +137,8 @@ copy_msg( store_t *ctx[], int t, message_t *tmsg, const char *tuid, int *uid ) */ typedef struct { + int t[2]; + void (*cb)( int sts, void *aux ), *aux; char *dname, *jname, *nname, *lname; FILE *jfp, *nfp; sync_rec_t *srecs, **srecadd, **osrecadd; @@ -361,11 +146,300 @@ typedef struct { store_t *ctx[2]; driver_t *drv[2]; int state[2], ret; + int find_old_total[2], find_old_done[2]; + int new_total[2], new_done[2]; + int find_new_total[2], find_new_done[2]; + int flags_total[2], flags_done[2]; + int trash_total[2], trash_done[2]; int maxuid[2], uidval[2], smaxxuid, lfd; + unsigned find:1, cancel:1; } sync_vars_t; +#define AUX &svars->t[t] +#define SVARS(aux) \ + int t = *(int *)aux; \ + sync_vars_t *svars = (sync_vars_t *)(((char *)(&((int *)aux)[-t])) - offsetof(sync_vars_t, t)); + +/* operation dependencies: + select(S): - + find_old(S): select(S) + select(M): find_old(S) | - + find_old(M): select(M) + new(M), new(S), flags(M): find_old(M) & find_old(S) + flags(S): count(new(S)) + find_new(x): new(x) + trash(x): flags(x) + close(x): trash(x) & find_new(x) // with expunge + cleanup: close(M) & close(S) +*/ + +#define ST_SENT_FIND_OLD (1<<0) +#define ST_SENT_NEW (1<<1) +#define ST_SENT_FIND_NEW (1<<2) +#define ST_SENT_FLAGS (1<<3) +#define ST_SENT_TRASH (1<<4) +#define ST_CLOSED (1<<5) +#define ST_CANCELED (1<<6) + #define ST_DID_EXPUNGE (1<<16) + +typedef struct copy_vars { + void (*cb)( int sts, int uid, struct copy_vars *vars ); + void *aux; + sync_rec_t *srec; /* also ->tuid */ + message_t *msg; + msg_data_t data; +} copy_vars_t; + +static void msg_fetched( int sts, void *aux ); + +static void +copy_msg( copy_vars_t *vars ) +{ + SVARS(vars->aux) + + vars->data.flags = vars->msg->flags; + svars->drv[1-t]->fetch_msg( svars->ctx[1-t], vars->msg, &vars->data, msg_fetched, vars ); +} + +static void msg_stored( int sts, int uid, void *aux ); + +static void +msg_fetched( int sts, void *aux ) +{ + copy_vars_t *vars = (copy_vars_t *)aux; + SVARS(vars->aux) + char *fmap, *buf; + int i, len, extra, cra, crd, scr, tcr; + int start, sbreak = 0, ebreak = 0; + char c; + + switch (sts) { + case DRV_OK: + vars->msg->flags = vars->data.flags; + + scr = (svars->drv[1-t]->flags / DRV_CRLF) & 1; + tcr = (svars->drv[t]->flags / DRV_CRLF) & 1; + if (vars->srec || scr != tcr) { + fmap = vars->data.data; + len = vars->data.len; + cra = crd = 0; + if (scr > tcr) + crd = -1; + else if (scr < tcr) + cra = 1; + extra = 0, i = 0; + if (vars->srec) { + extra += 8 + TUIDL + 1 + tcr; + nloop: + start = i; + while (i < len) { + c = fmap[i++]; + if (c == '\r') + extra += crd; + else if (c == '\n') { + extra += cra; + if (i - 1 - scr == start) { + sbreak = ebreak = i - 1 - scr; + goto oke; + } + if (!memcmp( fmap + start, "X-TUID: ", 8 )) { + extra -= (ebreak = i) - (sbreak = start); + goto oke; + } + goto nloop; + } + } + /* invalid message */ + free( fmap ); + vars->cb( SYNC_NOGOOD, 0, vars ); + break; + } + oke: + if (cra || crd) + for (; i < len; i++) { + c = fmap[i]; + if (c == '\r') + extra += crd; + else if (c == '\n') + extra += cra; + } + + vars->data.len = len + extra; + buf = vars->data.data = nfmalloc( vars->data.len ); + i = 0; + if (vars->srec) { + if (cra) { + for (; i < sbreak; i++) { + if (fmap[i] == '\n') + *buf++ = '\r'; + *buf++ = fmap[i]; + } + } else if (crd) { + for (; i < sbreak; i++) + if (fmap[i] != '\r') + *buf++ = fmap[i]; + } else { + memcpy( buf, fmap, sbreak ); + buf += sbreak; + } + memcpy( buf, "X-TUID: ", 8 ); + buf += 8; + memcpy( buf, vars->srec->tuid, TUIDL ); + buf += TUIDL; + if (tcr) + *buf++ = '\r'; + *buf++ = '\n'; + i = ebreak; + } + if (cra) { + for (; i < len; i++) { + if (fmap[i] == '\n') + *buf++ = '\r'; + *buf++ = fmap[i]; + } + } else if (crd) { + for (; i < len; i++) + if (fmap[i] != '\r') + *buf++ = fmap[i]; + } else + memcpy( buf, fmap + i, len - i ); + + free( fmap ); + } + + svars->drv[t]->store_msg( svars->ctx[t], &vars->data, !vars->srec, msg_stored, vars ); + break; + case DRV_CANCELED: + vars->cb( SYNC_CANCELED, 0, vars ); + break; + case DRV_MSG_BAD: + vars->cb( SYNC_NOGOOD, 0, vars ); + break; + case DRV_STORE_BAD: + vars->cb( SYNC_BAD(1-t), 0, vars ); + break; + default: + vars->cb( SYNC_FAIL, 0, vars ); + break; + } +} + +static void +msg_stored( int sts, int uid, void *aux ) +{ + copy_vars_t *vars = (copy_vars_t *)aux; + SVARS(vars->aux) + + (void)svars; + switch (sts) { + case DRV_OK: + vars->cb( SYNC_OK, uid, vars ); + break; + case DRV_CANCELED: + vars->cb( SYNC_CANCELED, 0, vars ); + break; + case DRV_STORE_BAD: + vars->cb( SYNC_BAD(t), 0, vars ); + break; + default: + vars->cb( SYNC_FAIL, 0, vars ); + break; + } +} + + +static void +stats( sync_vars_t *svars ) +{ + char buf[2][64]; + char *cs; + int t, l; + static int cols = -1; + + if (cols < 0 && (!(cs = getenv( "COLUMNS" )) || !(cols = atoi( cs ) / 2))) + cols = 36; + if (!(DFlags & QUIET)) { + for (t = 0; t < 2; t++) { + l = sprintf( buf[t], "?%d/%d +%d/%d *%d/%d #%d/%d", + svars->find_old_done[t] + svars->find_new_done[t], + svars->find_old_total[t] + svars->find_new_total[t], + svars->new_done[t], svars->new_total[t], + svars->flags_done[t], svars->flags_total[t], + svars->trash_done[t], svars->trash_total[t] ); + if (l > cols) + buf[t][cols - 1] = '~'; + } + infon( "\rM: %.*s S: %.*s", cols, buf[0], cols, buf[1] ); + } +} + + +static void sync_bail( sync_vars_t *svars ); +static void sync_bail1( sync_vars_t *svars ); +static void sync_bail2( sync_vars_t *svars ); +static void cancel_done( int sts, void *aux ); + +static void +cancel_sync( sync_vars_t *svars ) +{ + int t; + + svars->cancel = 1; + for (t = 0; t < 2; t++) + if (svars->ret & SYNC_BAD(t)) + cancel_done( DRV_STORE_BAD, AUX ); + else + svars->drv[t]->cancel( svars->ctx[t], cancel_done, AUX ); +} + +static void +cancel_done( int sts, void *aux ) +{ + SVARS(aux) + + if (sts != DRV_OK) { + svars->ret |= SYNC_BAD(t); + svars->drv[t]->cancel_store( svars->ctx[t] ); + } + svars->state[t] |= ST_CANCELED; + if (svars->state[1-t] & ST_CANCELED) { + Fclose( svars->nfp ); + Fclose( svars->jfp ); + sync_bail( svars ); + } +} + + +static int +check_ret( int sts, sync_vars_t *svars, int t ) +{ + switch (sts) { + case DRV_CANCELED: + return 1; + case DRV_STORE_BAD: + svars->ret |= SYNC_BAD(t); + cancel_sync( svars ); + return 1; + case DRV_BOX_BAD: + svars->ret |= SYNC_FAIL; + cancel_sync( svars ); + return 1; + } + return 0; +} + +static int +check_ret_aux( int sts, sync_vars_t *svars, int t, void *aux ) +{ + if (!check_ret( sts, svars, t )) + return 0; + free( aux ); + return 1; +} + + static char * clean_strdup( const char *s ) { @@ -379,26 +453,29 @@ clean_strdup( const char *s ) return cs; } + #define JOURNAL_VERSION "2" -int -sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) +static void select_box( sync_vars_t *svars, int t, int minwuid, int *mexcs, int nmexcs ); + +void +sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan, + void (*cb)( int sts, void *aux ), void *aux ) { - sync_vars_t svars[1]; - message_t *tmsg; + sync_vars_t *svars; sync_rec_t *srec, *nsrec; char *s, *cmname, *csname; FILE *jfp; - int no[2], del[2], nex, minwuid, uid, nmsgs; - int todel, *mexcs, nmexcs, rmexcs; int opts[2], line, t1, t2, t3, t; - unsigned char nflags, sflags, aflags, dflags; struct stat st; struct flock lck; char fbuf[16]; /* enlarge when support for keywords is added */ char buf[64]; - memset( svars, 0, sizeof(svars[0]) ); + svars = nfcalloc( sizeof(*svars) ); + svars->t[1] = 1; + svars->cb = cb; + svars->aux = aux; svars->ctx[0] = ctx[0]; svars->ctx[1] = ctx[1]; svars->chan = chan; @@ -416,7 +493,8 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) if (!strcmp( chan->sync_state ? chan->sync_state : global_sync_state, "*" )) { if (!ctx[S]->path) { error( "Error: store '%s' does not support in-box sync state\n", chan->stores[S]->name ); - return SYNC_BAD(S); + cb( SYNC_BAD(S), aux ); + return; } nfasprintf( &svars->dname, "%s/." EXE "state", ctx[S]->path ); } else { @@ -434,13 +512,15 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) if (!(s = strrchr( svars->dname, '/' ))) { error( "Error: invalid SyncState '%s'\n", svars->dname ); free( svars->dname ); - return SYNC_BAD(S); + cb( SYNC_BAD(S), aux ); + return; } *s = 0; if (mkdir( svars->dname, 0700 ) && errno != EEXIST) { error( "Error: cannot create SyncState directory '%s': %s\n", svars->dname, strerror(errno) ); free( svars->dname ); - return SYNC_BAD(S); + cb( SYNC_BAD(S), aux ); + return; } *s = '/'; nfasprintf( &svars->jname, "%s.journal", svars->dname ); @@ -456,13 +536,15 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) if ((svars->lfd = open( svars->lname, O_WRONLY|O_CREAT, 0666 )) < 0) { error( "Error: cannot create lock file %s: %s\n", svars->lname, strerror(errno) ); svars->ret = SYNC_FAIL; - goto bail2; + sync_bail2( svars ); + return; } if (fcntl( svars->lfd, F_SETLK, &lck )) { error( "Error: channel :%s:%s-:%s:%s is locked\n", chan->stores[M]->name, ctx[M]->name, chan->stores[S]->name, ctx[S]->name ); svars->ret = SYNC_FAIL; - goto bail1; + sync_bail1( svars ); + return; } if ((jfp = fopen( svars->dname, "r" ))) { debug( "reading sync state %s ...\n", svars->dname ); @@ -470,13 +552,15 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) error( "Error: incomplete sync state header in %s\n", svars->dname ); fclose( jfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } if (sscanf( buf, "%d:%d %d:%d:%d", &svars->uidval[M], &svars->maxuid[M], &svars->uidval[S], &svars->smaxxuid, &svars->maxuid[S]) != 5) { error( "Error: invalid sync state header in %s\n", svars->dname ); fclose( jfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } line = 1; while (fgets( buf, sizeof(buf), jfp )) { @@ -485,14 +569,16 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) error( "Error: incomplete sync state entry at %s:%d\n", svars->dname, line ); fclose( jfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } fbuf[0] = 0; if (sscanf( buf, "%d %d %15s", &t1, &t2, fbuf ) < 2) { error( "Error: invalid sync state entry at %s:%d\n", svars->dname, line ); fclose( jfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } srec = nfmalloc( sizeof(*srec) ); srec->uid[M] = t1; @@ -516,7 +602,8 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) if (errno != ENOENT) { error( "Error: cannot read sync state %s\n", svars->dname ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } } line = 0; @@ -527,14 +614,16 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) error( "Error: incomplete journal header in %s\n", svars->jname ); fclose( jfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } if (memcmp( buf, JOURNAL_VERSION "\n", strlen(JOURNAL_VERSION) + 1 )) { error( "Error: incompatible journal version " "(got %.*s, expected " JOURNAL_VERSION ")\n", t - 1, buf ); fclose( jfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } srec = 0; line = 1; @@ -544,7 +633,8 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) error( "Error: incomplete journal entry at %s:%d\n", svars->jname, line ); fclose( jfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } if (buf[0] == '#' ? (t3 = 0, (sscanf( buf + 2, "%d %d %n", &t1, &t2, &t3 ) < 2) || !t3 || (t - t3 != TUIDL + 3)) : @@ -557,7 +647,8 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) error( "Error: malformed journal entry at %s:%d\n", svars->jname, line ); fclose( jfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } if (buf[0] == '(') svars->maxuid[M] = t1; @@ -588,7 +679,8 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) error( "Error: journal entry at %s:%d refers to non-existing sync state entry\n", svars->jname, line ); fclose( jfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; syncfnd: debugn( " entry(%d,%d,%u) ", srec->uid[M], srec->uid[S], srec->flags ); switch (buf[0]) { @@ -648,7 +740,8 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) error( "Error: unrecognized journal entry at %s:%d\n", svars->jname, line ); fclose( jfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } } } @@ -658,19 +751,22 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) if (errno != ENOENT) { error( "Error: cannot read journal %s\n", svars->jname ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } } if (!(svars->nfp = fopen( svars->nname, "w" ))) { error( "Error: cannot write new sync state %s\n", svars->nname ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } if (!(svars->jfp = fopen( svars->jname, "a" ))) { error( "Error: cannot write journal %s\n", svars->jname ); fclose( svars->nfp ); svars->ret = SYNC_FAIL; - goto bail; + sync_bail( svars ); + return; } setlinebuf( svars->jfp ); if (!line) @@ -725,14 +821,192 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) svars->drv[M]->prepare_opts( ctx[M], opts[M] ); svars->drv[S]->prepare_opts( ctx[S], opts[S] ); - if ((svars->ret = select_box( svars->srecs, svars->ctx, svars->maxuid, svars->uidval, S, (ctx[S]->opts & OPEN_OLD) ? 1 : INT_MAX, 0, 0, line ? svars->jfp : 0 )) != SYNC_OK) - goto finish; + svars->find = line != 0; + if (!svars->smaxxuid) + select_box( svars, M, (ctx[M]->opts & OPEN_OLD) ? 1 : INT_MAX, 0, 0 ); + select_box( svars, S, (ctx[S]->opts & OPEN_OLD) ? 1 : INT_MAX, 0, 0 ); +} + +static void box_selected( int sts, void *aux ); + +static void +select_box( sync_vars_t *svars, int t, int minwuid, int *mexcs, int nmexcs ) +{ + sync_rec_t *srec; + int maxwuid; + + if (svars->ctx[t]->opts & OPEN_NEW) { + if (minwuid > svars->maxuid[t] + 1) + minwuid = svars->maxuid[t] + 1; + maxwuid = INT_MAX; + } else if (svars->ctx[t]->opts & OPEN_OLD) { + maxwuid = 0; + for (srec = svars->srecs; srec; srec = srec->next) + if (!(srec->status & S_DEAD) && srec->uid[t] > maxwuid) + maxwuid = srec->uid[t]; + } else + maxwuid = 0; + info( "Selecting %s %s...\n", str_ms[t], svars->ctx[t]->name ); + debug( maxwuid == INT_MAX ? "selecting %s [%d,inf]\n" : "selecting %s [%d,%d]\n", str_ms[t], minwuid, maxwuid ); + svars->drv[t]->select( svars->ctx[t], minwuid, maxwuid, mexcs, nmexcs, box_selected, AUX ); +} + +typedef struct { + void *aux; + sync_rec_t *srec; +} find_vars_t; + +static void msg_found_sel( int sts, int uid, void *aux ); +static void msgs_found_sel( sync_vars_t *svars, int t ); + +static void +box_selected( int sts, void *aux ) +{ + SVARS(aux) + find_vars_t *fv; + sync_rec_t *srec; + + if (check_ret( sts, svars, t )) + return; + if (svars->uidval[t] && svars->uidval[t] != svars->ctx[t]->uidvalidity) { + error( "Error: UIDVALIDITY of %s changed (got %d, expected %d)\n", + str_ms[t], svars->ctx[t]->uidvalidity, svars->uidval[t] ); + svars->ret |= SYNC_FAIL; + cancel_sync( svars ); + return; + } + info( "%s: %d messages, %d recent\n", str_ms[t], svars->ctx[t]->count, svars->ctx[t]->recent ); - mexcs = 0; - nmexcs = rmexcs = 0; - minwuid = INT_MAX; - if (svars->smaxxuid) { + if (svars->find) { + /* + * Alternatively, the TUIDs could be fetched into the messages and + * looked up here. This would make the search faster (probably) and + * save roundtrips. On the downside, quite some additional data would + * have to be fetched for every message and the IMAP driver would be + * more complicated. This is a corner case anyway, so why bother. + */ + debug( "finding previously copied messages\n" ); + for (srec = svars->srecs; srec; srec = srec->next) { + if (srec->status & S_DEAD) + continue; + if (srec->uid[t] == -2 && srec->tuid[0]) { + debug( " pair(%d,%d): lookup %s, TUID %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], str_ms[t], srec->tuid ); + svars->find_old_total[t]++; + stats( svars ); + fv = nfmalloc( sizeof(*fv) ); + fv->aux = AUX; + fv->srec = srec; + svars->drv[t]->find_msg( svars->ctx[t], srec->tuid, msg_found_sel, fv ); + if (svars->cancel) + return; + } + } + } + svars->state[t] |= ST_SENT_FIND_OLD; + msgs_found_sel( svars, t ); +} + +static void +msg_found_sel( int sts, int uid, void *aux ) +{ + find_vars_t *vars = (find_vars_t *)aux; + SVARS(vars->aux) + + if (check_ret_aux( sts, svars, t, vars )) + return; + switch (sts) { + case DRV_OK: + debug( " -> new UID %d\n", uid ); + Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], vars->srec->uid[M], vars->srec->uid[S], uid ); + vars->srec->uid[t] = uid; + vars->srec->tuid[0] = 0; + break; + default: + debug( " -> TUID lost\n" ); + Fprintf( svars->jfp, "& %d %d\n", vars->srec->uid[M], vars->srec->uid[S] ); + vars->srec->flags = 0; + vars->srec->tuid[0] = 0; + break; + } + free( vars ); + svars->find_old_done[t]++; + stats( svars ); + msgs_found_sel( svars, t ); +} + +typedef struct { + void *aux; + sync_rec_t *srec; + int aflags, dflags; +} flag_vars_t; + +static void flags_set_del( int sts, void *aux ); +static void flags_set_sync( int sts, void *aux ); +static void flags_set_sync_p2( sync_vars_t *svars, sync_rec_t *srec, int t ); +static void msgs_flags_set( sync_vars_t *svars, int t ); +static void msg_copied( int sts, int uid, copy_vars_t *vars ); +static void msg_copied_p2( sync_vars_t *svars, sync_rec_t *srec, int t, message_t *tmsg, int uid ); +static void msgs_copied( sync_vars_t *svars, int t ); + +static void +msgs_found_sel( sync_vars_t *svars, int t ) +{ + sync_rec_t *srec, *nsrec = 0; + message_t *tmsg; + copy_vars_t *cv; + flag_vars_t *fv; + const char *diag; + int uid, minwuid, *mexcs, nmexcs, rmexcs, no[2], del[2], todel, nmsgs, t1, t2; + int sflags, nflags, aflags, dflags, nex; + char fbuf[16]; /* enlarge when support for keywords is added */ + + if (!(svars->state[t] & ST_SENT_FIND_OLD) || svars->find_old_done[t] < svars->find_new_total[t]) + return; + + /* + * Mapping tmsg -> srec (this variant) is dog slow for new messages. + * Mapping srec -> tmsg is dog slow for deleted messages. + * One solution would be using binary search on an index array. + * msgs are already sorted by UID, srecs would have to be sorted by uid[t]. + */ + debug( "matching messages against sync records\n" ); + for (tmsg = svars->ctx[t]->msgs; tmsg; tmsg = tmsg->next) { + uid = tmsg->uid; + if (DFlags & DEBUG) { + make_flags( tmsg->flags, fbuf ); + printf( svars->ctx[t]->opts & OPEN_SIZE ? " message %5d, %-4s, %6d: " : " message %5d, %-4s: ", uid, fbuf, tmsg->size ); + } + for (srec = nsrec; srec; srec = srec->next) { + if (srec->status & S_DEAD) + continue; + if (srec->uid[t] == uid) { + diag = srec == nsrec ? "adjacently" : "after gap"; + goto found; + } + } + for (srec = svars->srecs; srec != nsrec; srec = srec->next) { + if (srec->status & S_DEAD) + continue; + if (srec->uid[t] == uid) { + diag = "after reset"; + goto found; + } + } + tmsg->srec = 0; + debug( "new\n" ); + continue; + found: + tmsg->srec = srec; + srec->msg[t] = tmsg; + nsrec = srec->next; + debug( "pairs %5d %s\n", srec->uid[1-t], diag ); + } + + if ((t == S) && svars->smaxxuid) { debug( "preparing master selection - max expired slave uid is %d\n", svars->smaxxuid ); + mexcs = 0; + nmexcs = rmexcs = 0; + minwuid = INT_MAX; for (srec = svars->srecs; srec; srec = srec->next) { if (srec->status & S_DEAD) continue; @@ -783,10 +1057,12 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) for (t = 0; t < nmexcs; t++) debugn( " %d", mexcs[t] ); debug( "\n" ); - } else if (ctx[M]->opts & OPEN_OLD) - minwuid = 1; - if ((svars->ret = select_box( svars->srecs, svars->ctx, svars->maxuid, svars->uidval, M, minwuid, mexcs, nmexcs, line ? svars->jfp : 0 )) != SYNC_OK) - goto finish; + select_box( svars, M, minwuid, mexcs, nmexcs ); + return; + } + + if (!(svars->state[1-t] & ST_SENT_FIND_OLD) || svars->find_old_done[1-t] < svars->find_new_total[1-t]) + return; if (!svars->uidval[M] || !svars->uidval[S]) { svars->uidval[M] = svars->ctx[M]->uidvalidity; @@ -823,11 +1099,6 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) debug( " -> pair(%d,%d) created\n", srec->uid[M], srec->uid[S] ); } if ((tmsg->flags & F_FLAGGED) || !svars->chan->stores[t]->max_size || tmsg->size <= svars->chan->stores[t]->max_size) { - if (!nmsgs) - infon( t ? "Pulling new messages..." : "Pushing new messages..." ); - else - infoc( '.' ); - nmsgs++; if (tmsg->flags) { srec->flags = tmsg->flags; Fprintf( svars->jfp, "* %d %d %u\n", srec->uid[M], srec->uid[S], srec->flags ); @@ -837,65 +1108,30 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) t2 = arc4_getbyte() & 0x3f; srec->tuid[t1] = t2 < 26 ? t2 + 'A' : t2 < 52 ? t2 + 'a' - 26 : t2 < 62 ? t2 + '0' - 52 : t2 == 62 ? '+' : '/'; } + svars->new_total[t]++; + stats( svars ); + cv = nfmalloc( sizeof(*cv) ); + cv->cb = msg_copied; + cv->aux = AUX; + cv->srec = srec; + cv->msg = tmsg; Fprintf( svars->jfp, "# %d %d %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], srec->tuid ); debug( " -> %sing message, TUID %." stringify(TUIDL) "s\n", str_hl[t], srec->tuid ); - switch ((svars->ret = copy_msg( svars->ctx, t, tmsg, srec->tuid, &uid ))) { - case SYNC_OK: break; - case SYNC_NOGOOD: - /* The error is either transient or the message is gone. */ - debug( " -> killing (%d,%d)\n", srec->uid[M], srec->uid[S] ); - srec->status = S_DEAD; - Fprintf( svars->jfp, "- %d %d\n", srec->uid[M], srec->uid[S] ); - continue; - default: goto finish; - } + copy_msg( cv ); + if (svars->cancel) + return; } else { if (tmsg->srec) { debug( " -> not %sing - still too big\n", str_hl[t] ); continue; } debug( " -> not %sing - too big\n", str_hl[t] ); - uid = -1; - } - if (srec->uid[t] != uid) { - debug( " -> new UID %d\n", uid ); - Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], srec->uid[M], srec->uid[S], uid ); - srec->uid[t] = uid; - srec->tuid[0] = 0; - } - if (!tmsg->srec) { - tmsg->srec = srec; - if (svars->maxuid[1-t] < tmsg->uid) { - svars->maxuid[1-t] = tmsg->uid; - Fprintf( svars->jfp, "%c %d\n", ")("[t], tmsg->uid ); - } + msg_copied_p2( svars, srec, t, tmsg, -1 ); } } } - if (nmsgs) - info( " %d messages\n", nmsgs ); - } - debug( "finding just copied messages\n" ); - for (srec = svars->srecs; srec; srec = srec->next) { - if (srec->status & S_DEAD) - continue; - if (srec->tuid[0]) { - t = (srec->uid[M] == -2) ? M : S; - debug( " pair(%d,%d): lookup %s, TUID %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], str_ms[t], srec->tuid ); - switch (svars->drv[t]->find_msg( svars->ctx[t], srec->tuid, &uid )) { - case DRV_STORE_BAD: svars->ret = SYNC_BAD(t); goto finish; - case DRV_OK: - debug( " -> new UID %d\n", uid ); - break; - default: - warn( "Warning: cannot find newly stored message %." stringify(TUIDL) "s on %s.\n", srec->tuid, str_ms[t] ); - uid = 0; - break; - } - Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], srec->uid[M], srec->uid[S], uid ); - srec->uid[t] = uid; - srec->tuid[0] = 0; - } + svars->state[t] |= ST_SENT_NEW; + msgs_copied( svars, t ); } debug( "synchronizing old entries\n" ); @@ -928,15 +1164,14 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) info( "Info: conflicting changes in (%d,%d)\n", srec->uid[M], srec->uid[S] ); if (svars->chan->ops[t] & OP_DELETE) { debug( " %sing delete\n", str_hl[t] ); - switch (svars->drv[t]->set_flags( svars->ctx[t], srec->msg[t], srec->uid[t], F_DELETED, 0 )) { - case DRV_STORE_BAD: svars->ret = SYNC_BAD(t); goto finish; - case DRV_BOX_BAD: svars->ret = SYNC_FAIL; goto finish; - default: /* ok */ break; - case DRV_OK: - srec->status |= S_DEL(t); - Fprintf( svars->jfp, "%c %d %d 0\n", "><"[t], srec->uid[M], srec->uid[S] ); - srec->uid[1-t] = 0; - } + svars->flags_total[t]++; + stats( svars ); + fv = nfmalloc( sizeof(*fv) ); + fv->aux = AUX; + fv->srec = srec; + svars->drv[t]->set_flags( svars->ctx[t], srec->msg[t], srec->uid[t], F_DELETED, 0, flags_set_del, fv ); + if (svars->cancel) + return; } else debug( " not %sing delete\n", str_hl[t] ); } else if (!srec->msg[1-t]) @@ -969,7 +1204,7 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) if ((svars->chan->ops[S] & (OP_NEW|OP_RENEW|OP_FLAGS)) && svars->chan->max_messages) { /* Flagged and not yet synced messages older than the first not * expired message are not counted. */ - todel = svars->ctx[S]->count - svars->chan->max_messages; + todel = svars->ctx[S]->count + svars->new_total[S] - svars->chan->max_messages; debug( "scheduling %d excess messages for expiration\n", todel ); for (tmsg = svars->ctx[S]->msgs; tmsg && todel > 0; tmsg = tmsg->next) if (!(tmsg->status & M_DEAD) && (srec = tmsg->srec) && @@ -1038,83 +1273,329 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) aflags &= ~srec->msg[t]->flags; dflags &= srec->msg[t]->flags; } - switch ((aflags | dflags) ? svars->drv[t]->set_flags( svars->ctx[t], srec->msg[t], srec->uid[t], aflags, dflags ) : DRV_OK) { - case DRV_STORE_BAD: svars->ret = SYNC_BAD(t); goto finish; - case DRV_BOX_BAD: svars->ret = SYNC_FAIL; goto finish; - default: /* ok */ srec->aflags[t] = srec->dflags[t] = 0; break; - case DRV_OK: - if (aflags & F_DELETED) - srec->status |= S_DEL(t); - else if (dflags & F_DELETED) - srec->status &= ~S_DEL(t); - if (t) { - nex = (srec->status / S_NEXPIRE) & 1; - if (nex != ((srec->status / S_EXPIRED) & 1)) { - if (nex && (svars->smaxxuid < srec->uid[S])) - svars->smaxxuid = srec->uid[S]; - Fprintf( svars->jfp, "/ %d %d\n", srec->uid[M], srec->uid[S] ); - debug( " pair(%d,%d): expired %d (commit)\n", srec->uid[M], srec->uid[S], nex ); - srec->status = (srec->status & ~S_EXPIRED) | (nex * S_EXPIRED); - } else if (nex != ((srec->status / S_EXPIRE) & 1)) { - Fprintf( svars->jfp, "\\ %d %d\n", srec->uid[M], srec->uid[S] ); - debug( " pair(%d,%d): expire %d (cancel)\n", srec->uid[M], srec->uid[S], nex ); - srec->status = (srec->status & ~S_EXPIRE) | (nex * S_EXPIRE); - } - } - } + if (aflags | dflags) { + svars->flags_total[t]++; + stats( svars ); + fv = nfmalloc( sizeof(*fv) ); + fv->aux = AUX; + fv->srec = srec; + fv->aflags = aflags; + fv->dflags = dflags; + svars->drv[t]->set_flags( svars->ctx[t], srec->msg[t], srec->uid[t], aflags, dflags, flags_set_sync, fv ); + if (svars->cancel) + return; + } else + flags_set_sync_p2( svars, srec, t ); } - nflags = (srec->flags | srec->aflags[M] | srec->aflags[S]) & ~(srec->dflags[M] | srec->dflags[S]); - if (srec->flags != nflags) { - debug( " pair(%d,%d): updating flags (%u -> %u)\n", srec->uid[M], srec->uid[S], srec->flags, nflags ); - srec->flags = nflags; - Fprintf( svars->jfp, "* %d %d %u\n", srec->uid[M], srec->uid[S], nflags ); + } + for (t = 0; t < 2; t++) { + svars->drv[t]->commit( svars->ctx[t] ); + svars->state[t] |= ST_SENT_FLAGS; + msgs_flags_set( svars, t ); + } +} + +static void +msg_copied( int sts, int uid, copy_vars_t *vars ) +{ + SVARS(vars->aux) + + switch (sts) { + case SYNC_OK: + msg_copied_p2( svars, vars->srec, t, vars->msg, uid ); + break; + case SYNC_NOGOOD: + debug( " -> killing (%d,%d)\n", vars->srec->uid[M], vars->srec->uid[S] ); + vars->srec->status = S_DEAD; + Fprintf( svars->jfp, "- %d %d\n", vars->srec->uid[M], vars->srec->uid[S] ); + break; + default: + cancel_sync( svars ); + case SYNC_CANCELED: + free( vars ); + return; + } + free( vars ); + svars->new_done[t]++; + stats( svars ); + msgs_copied( svars, t ); +} + +static void +msg_copied_p2( sync_vars_t *svars, sync_rec_t *srec, int t, message_t *tmsg, int uid ) +{ + if (srec->uid[t] != uid) { + debug( " -> new UID %d\n", uid ); + Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], srec->uid[M], srec->uid[S], uid ); + srec->uid[t] = uid; + srec->tuid[0] = 0; + } + if (!tmsg->srec) { + tmsg->srec = srec; + if (svars->maxuid[1-t] < tmsg->uid) { + svars->maxuid[1-t] = tmsg->uid; + Fprintf( svars->jfp, "%c %d\n", ")("[t], tmsg->uid ); } } +} - for (t = 0; t < 2; t++) { - if (svars->chan->ops[t] & OP_EXPUNGE) { - if (svars->ctx[t]->conf->trash || (svars->ctx[1-t]->conf->trash && svars->ctx[1-t]->conf->trash_remote_new)) { - debug( "trashing in %s\n", str_ms[t] ); - for (tmsg = svars->ctx[t]->msgs; tmsg; tmsg = tmsg->next) - if (tmsg->flags & F_DELETED) { - if (svars->ctx[t]->conf->trash) { - if (!svars->ctx[t]->conf->trash_only_new || !tmsg->srec || tmsg->srec->uid[1-t] < 0) { - debug( " trashing message %d\n", tmsg->uid ); - switch (svars->drv[t]->trash_msg( svars->ctx[t], tmsg )) { - case DRV_OK: break; - case DRV_STORE_BAD: svars->ret = SYNC_BAD(t); goto finish; - default: svars->ret = SYNC_FAIL; goto nexex; - } - } else - debug( " not trashing message %d - not new\n", tmsg->uid ); - } else { - if (!tmsg->srec || tmsg->srec->uid[1-t] < 0) { - if (!svars->ctx[1-t]->conf->max_size || tmsg->size <= svars->ctx[1-t]->conf->max_size) { - debug( " remote trashing message %d\n", tmsg->uid ); - switch ((svars->ret = copy_msg( svars->ctx, 1 - t, tmsg, 0, 0 ))) { - case SYNC_OK: break; - case SYNC_NOGOOD: svars->ret = SYNC_FAIL; goto nexex; - case SYNC_FAIL: goto nexex; - default: goto finish; - } - } else - debug( " not remote trashing message %d - too big\n", tmsg->uid ); - } else - debug( " not remote trashing message %d - not new\n", tmsg->uid ); - } - } - } +static void msg_found_new( int sts, int uid, void *aux ); +static void sync_close( sync_vars_t *svars, int t ); - info( "Expunging %s...\n", str_ms[t] ); - debug( "expunging %s\n", str_ms[t] ); - switch (svars->drv[t]->close( svars->ctx[t] )) { - case DRV_OK: svars->state[t] |= ST_DID_EXPUNGE; break; - case DRV_STORE_BAD: svars->ret = SYNC_BAD(t); goto finish; - default: break; - } +static void +msgs_copied( sync_vars_t *svars, int t ) +{ + sync_rec_t *srec; + find_vars_t *fv; + + if (!(svars->state[t] & ST_SENT_NEW) || svars->new_done[t] < svars->new_total[t]) + return; + + debug( "finding just copied messages on %s\n", str_ms[t] ); + for (srec = svars->srecs; srec; srec = srec->next) { + if (srec->status & S_DEAD) + continue; + if (srec->tuid[0] && srec->uid[t] == -2) { + debug( " pair(%d,%d): lookup %s, TUID %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], str_ms[t], srec->tuid ); + svars->find_new_total[t]++; + stats( svars ); + fv = nfmalloc( sizeof(*fv) ); + fv->aux = AUX; + fv->srec = srec; + svars->drv[t]->find_msg( svars->ctx[t], srec->tuid, msg_found_new, fv ); + if (svars->cancel) + return; } - nexex: ; } + svars->state[t] |= ST_SENT_FIND_NEW; + sync_close( svars, t ); +} + +static void +msg_found_new( int sts, int uid, void *aux ) +{ + find_vars_t *vars = (find_vars_t *)aux; + SVARS(vars->aux) + + if (check_ret_aux( sts, svars, t, vars )) + return; + switch (sts) { + case DRV_OK: + debug( " -> new UID %d\n", uid ); + break; + default: + warn( "Warning: cannot find newly stored message %." stringify(TUIDL) "s on %s.\n", vars->srec->tuid, str_ms[t] ); + uid = 0; + break; + } + Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], vars->srec->uid[M], vars->srec->uid[S], uid ); + vars->srec->uid[t] = uid; + vars->srec->tuid[0] = 0; + free( vars ); + svars->find_new_done[t]++; + stats( svars ); + sync_close( svars, t ); +} + +static void +flags_set_del( int sts, void *aux ) +{ + flag_vars_t *vars = (flag_vars_t *)aux; + SVARS(vars->aux) + + if (check_ret_aux( sts, svars, t, vars )) + return; + switch (sts) { + case DRV_OK: + vars->srec->status |= S_DEL(t); + Fprintf( svars->jfp, "%c %d %d 0\n", "><"[t], vars->srec->uid[M], vars->srec->uid[S] ); + vars->srec->uid[1-t] = 0; + break; + } + free( vars ); + svars->flags_done[t]++; + stats( svars ); + msgs_flags_set( svars, t ); +} + +static void +flags_set_sync( int sts, void *aux ) +{ + flag_vars_t *vars = (flag_vars_t *)aux; + SVARS(vars->aux) + + if (check_ret_aux( sts, svars, t, vars )) + return; + switch (sts) { + case DRV_OK: + if (vars->aflags & F_DELETED) + vars->srec->status |= S_DEL(t); + else if (vars->dflags & F_DELETED) + vars->srec->status &= ~S_DEL(t); + flags_set_sync_p2( svars, vars->srec, t ); + break; + } + free( vars ); + svars->flags_done[t]++; + stats( svars ); + msgs_flags_set( svars, t ); +} + +static void +flags_set_sync_p2( sync_vars_t *svars, sync_rec_t *srec, int t ) +{ + int nflags, nex; + + nflags = (srec->flags | srec->aflags[t]) & ~srec->dflags[t]; + if (srec->flags != nflags) { + debug( " pair(%d,%d): updating flags (%u -> %u)\n", srec->uid[M], srec->uid[S], srec->flags, nflags ); + srec->flags = nflags; + Fprintf( svars->jfp, "* %d %d %u\n", srec->uid[M], srec->uid[S], nflags ); + } + if (t == S) { + nex = (srec->status / S_NEXPIRE) & 1; + if (nex != ((srec->status / S_EXPIRED) & 1)) { + if (nex && (svars->smaxxuid < srec->uid[S])) + svars->smaxxuid = srec->uid[S]; + Fprintf( svars->jfp, "/ %d %d\n", srec->uid[M], srec->uid[S] ); + debug( " pair(%d,%d): expired %d (commit)\n", srec->uid[M], srec->uid[S], nex ); + srec->status = (srec->status & ~S_EXPIRED) | (nex * S_EXPIRED); + } else if (nex != ((srec->status / S_EXPIRE) & 1)) { + Fprintf( svars->jfp, "\\ %d %d\n", srec->uid[M], srec->uid[S] ); + debug( " pair(%d,%d): expire %d (cancel)\n", srec->uid[M], srec->uid[S], nex ); + srec->status = (srec->status & ~S_EXPIRE) | (nex * S_EXPIRE); + } + } +} + +static void msg_trashed( int sts, void *aux ); +static void msg_rtrashed( int sts, int uid, copy_vars_t *vars ); + +static void +msgs_flags_set( sync_vars_t *svars, int t ) +{ + message_t *tmsg; + copy_vars_t *cv; + + if (!(svars->state[t] & ST_SENT_FLAGS) || svars->flags_done[t] < svars->flags_total[t]) + return; + + if ((svars->chan->ops[t] & OP_EXPUNGE) && + (svars->ctx[t]->conf->trash || (svars->ctx[1-t]->conf->trash && svars->ctx[1-t]->conf->trash_remote_new))) { + debug( "trashing in %s\n", str_ms[t] ); + for (tmsg = svars->ctx[t]->msgs; tmsg; tmsg = tmsg->next) + if (tmsg->flags & F_DELETED) { + if (svars->ctx[t]->conf->trash) { + if (!svars->ctx[t]->conf->trash_only_new || !tmsg->srec || tmsg->srec->uid[1-t] < 0) { + debug( "%s: trashing message %d\n", str_ms[t], tmsg->uid ); + svars->trash_total[t]++; + stats( svars ); + svars->drv[t]->trash_msg( svars->ctx[t], tmsg, msg_trashed, AUX ); + if (svars->cancel) + return; + } else + debug( "%s: not trashing message %d - not new\n", str_ms[t], tmsg->uid ); + } else { + if (!tmsg->srec || tmsg->srec->uid[1-t] < 0) { + if (!svars->ctx[1-t]->conf->max_size || tmsg->size <= svars->ctx[1-t]->conf->max_size) { + debug( "%s: remote trashing message %d\n", str_ms[t], tmsg->uid ); + svars->trash_total[t]++; + stats( svars ); + cv = nfmalloc( sizeof(*cv) ); + cv->cb = msg_rtrashed; + cv->aux = AUX; + cv->srec = 0; + cv->msg = tmsg; + copy_msg( cv ); + if (svars->cancel) + return; + } else + debug( "%s: not remote trashing message %d - too big\n", str_ms[t], tmsg->uid ); + } else + debug( "%s: not remote trashing message %d - not new\n", str_ms[t], tmsg->uid ); + } + } + } + svars->state[t] |= ST_SENT_TRASH; + sync_close( svars, t ); +} + +static void +msg_trashed( int sts, void *aux ) +{ + SVARS(aux) + + if (sts == DRV_MSG_BAD) + sts = DRV_BOX_BAD; + if (check_ret( sts, svars, t )) + return; + svars->trash_done[t]++; + stats( svars ); + sync_close( svars, t ); +} + +static void +msg_rtrashed( int sts, int uid, copy_vars_t *vars ) +{ + SVARS(vars->aux) + + (void)uid; + switch (sts) { + case SYNC_OK: + case SYNC_NOGOOD: /* the message is gone or heavily busted */ + break; + default: + cancel_sync( svars ); + case SYNC_CANCELED: + free( vars ); + return; + } + free( vars ); + svars->trash_done[t]++; + stats( svars ); + sync_close( svars, t ); +} + +static void box_closed( int sts, void *aux ); +static void box_closed_p2( sync_vars_t *svars, int t ); + +static void +sync_close( sync_vars_t *svars, int t ) +{ + if ((~svars->state[t] & (ST_SENT_FIND_NEW|ST_SENT_TRASH)) || + svars->find_new_done[t] < svars->find_new_total[t] || + svars->trash_done[t] < svars->trash_total[t]) + return; + + if ((svars->chan->ops[t] & OP_EXPUNGE) /*&& !(svars->state[t] & ST_TRASH_BAD)*/) { + debug( "expunging %s\n", str_ms[t] ); + svars->drv[t]->close( svars->ctx[t], box_closed, AUX ); + } else + box_closed_p2( svars, t ); +} + +static void +box_closed( int sts, void *aux ) +{ + SVARS(aux) + + if (check_ret( sts, svars, t )) + return; + svars->state[t] |= ST_DID_EXPUNGE; + box_closed_p2( svars, t ); +} + +static void +box_closed_p2( sync_vars_t *svars, int t ) +{ + sync_rec_t *srec; + int minwuid; + char fbuf[16]; /* enlarge when support for keywords is added */ + + svars->state[t] |= ST_CLOSED; + if (!(svars->state[1-t] & ST_CLOSED)) + return; + if ((svars->state[M] | svars->state[S]) & ST_DID_EXPUNGE) { /* This cleanup is not strictly necessary, as the next full sync would throw out the dead entries anyway. But ... */ @@ -1172,24 +1653,42 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan ) unlink( svars->jname ); } - bail: + sync_bail( svars ); +} + +static void +sync_bail( sync_vars_t *svars ) +{ + sync_rec_t *srec, *nsrec; + for (srec = svars->srecs; srec; srec = nsrec) { nsrec = srec->next; free( srec ); } unlink( svars->lname ); - bail1: + sync_bail1( svars ); +} + +static void +sync_bail1( sync_vars_t *svars ) +{ close( svars->lfd ); - bail2: + sync_bail2( svars ); +} + +static void +sync_bail2( sync_vars_t *svars ) +{ + void (*cb)( int sts, void *aux ) = svars->cb; + void *aux = svars->aux; + int ret = svars->ret; + free( svars->lname ); free( svars->nname ); free( svars->jname ); free( svars->dname ); - return svars->ret; - - finish: - Fclose( svars->nfp ); - Fclose( svars->jfp ); - goto bail; + free( svars ); + error( "" ); + cb( ret, aux ); } diff --git a/src/util.c b/src/util.c @@ -43,6 +43,7 @@ debug( const char *msg, ... ) vprintf( msg, va ); va_end( va ); fflush( stdout ); + need_nl = 0; } } @@ -70,6 +71,7 @@ info( const char *msg, ... ) vprintf( msg, va ); va_end( va ); fflush( stdout ); + need_nl = 0; } } @@ -88,16 +90,6 @@ infon( const char *msg, ... ) } void -infoc( char c ) -{ - if (!(DFlags & QUIET)) { - putchar( c ); - fflush( stdout ); - need_nl = Ontty; - } -} - -void warn( const char *msg, ... ) { va_list va;