From f377e7b696f2345482e1b1ce1400be5761f5233b Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sat, 25 Oct 2014 17:30:57 +0200 Subject: [PATCH] introduce FieldDelimiter and InfoDelimiter options ... for windows fs compatibility. the maildir-specific InfoDelimiter inherits the global FieldDelimiter (which affects SyncState), based on the assumption that if the sync state is on a windows FS, the mailboxes certainly will be as well, while the inverse is not necessarily true (when running on unix, anyway). REFMAIL: --- NEWS | 2 ++ README | 3 --- src/common.h | 1 + src/config.c | 13 ++++++++++++ src/drv_maildir.c | 53 ++++++++++++++++++++++++++++++++--------------- src/main.c | 5 +++++ src/mbsync.1 | 20 +++++++++++++++++- src/sync.c | 5 +++-- 8 files changed, 79 insertions(+), 23 deletions(-) diff --git a/NEWS b/NEWS index 2d2b2c0..d84eb62 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,8 @@ Notice: Tunnels are assumed to be secure and thus default to no SSL. Support for SASL (flexible authentication) has been added. +Support for Windows file systems has been added. + [1.1.0] Support for hierarchical mailboxes in Patterns. diff --git a/README b/README index a973a70..66a0017 100644 --- a/README +++ b/README @@ -59,9 +59,6 @@ isync executable still exists; it is a compatibility wrapper around mbsync. At some point, ``isync'' has successfully run on: Linux, Solaris 2.7, OpenBSD 2.8, FreeBSD 4.3. - Note that Cygwin cannot be reasonably supported due to restrictions - of the Windows file system. - * Requirements Berkley DB 4.2+ diff --git a/src/common.h b/src/common.h index e90f0ea..41067a0 100644 --- a/src/common.h +++ b/src/common.h @@ -65,6 +65,7 @@ extern int DFlags; extern int UseFSync; +extern char FieldDelimiter; extern int Pid; extern char Hostname[256]; diff --git a/src/config.c b/src/config.c index 2ac3e70..49c620c 100644 --- a/src/config.c +++ b/src/config.c @@ -467,6 +467,19 @@ load_config( const char *where, int pseudo ) { UseFSync = parse_bool( &cfile ); } + else if (!strcasecmp( "FieldDelimiter", cfile.cmd )) + { + if (strlen( cfile.val ) != 1) { + error( "%s:%d: Field delimiter must be exactly one character long\n", cfile.file, cfile.line ); + cfile.err = 1; + } else { + FieldDelimiter = cfile.val[0]; + if (!ispunct( FieldDelimiter )) { + error( "%s:%d: Field delimiter must be a punctuation character\n", cfile.file, cfile.line ); + cfile.err = 1; + } + } + } else if (!getopt_helper( &cfile, &gcops, &global_conf )) { error( "%s:%d: unknown section keyword '%s'\n", diff --git a/src/drv_maildir.c b/src/drv_maildir.c index f936938..5bf88c1 100644 --- a/src/drv_maildir.c +++ b/src/drv_maildir.c @@ -57,6 +57,8 @@ typedef struct maildir_store_conf { #ifdef USE_DB int alt_map; #endif /* USE_DB */ + char info_delimiter; + char *info_prefix, *info_stop; /* precalculated from info_delimiter */ } maildir_store_conf_t; typedef struct maildir_message { @@ -84,14 +86,14 @@ static int MaildirCount; static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' }; static unsigned char -maildir_parse_flags( const char *base ) +maildir_parse_flags( const char *info_prefix, const char *base ) { const char *s; unsigned i; unsigned char flags; flags = 0; - if ((s = strstr( base, ":2," ))) + if ((s = strstr( base, info_prefix ))) for (s += 3, i = 0; i < as(Flags); i++) if (strchr( s, Flags[i] )) flags |= (1 << i); @@ -413,9 +415,9 @@ maildir_validate( const char *box, int create, maildir_store_t *ctx ) #ifdef USE_DB static void -make_key( DBT *tkey, char *name ) +make_key( const char *info_stop, DBT *tkey, char *name ) { - char *u = strpbrk( name, ":," ); + char *u = strpbrk( name, info_stop ); tkey->data = name; tkey->size = u ? (size_t)(u - name) : strlen( name ); } @@ -439,7 +441,7 @@ maildir_set_uid( maildir_store_t *ctx, const char *name, int *uid ) return DRV_BOX_BAD; } if (uid) { - make_key( &key, (char *)name ); + make_key( ((maildir_store_conf_t *)ctx->gen.conf)->info_stop, &key, (char *)name ); value.data = uid; value.size = sizeof(*uid); if ((ret = ctx->db->put( ctx->db, 0, &key, &value, 0 ))) @@ -615,6 +617,7 @@ maildir_compare( const void *l, const void *r ) static int maildir_scan( maildir_store_t *ctx, msglist_t *msglist ) { + maildir_store_conf_t *conf = (maildir_store_conf_t *)ctx->gen.conf; DIR *d; FILE *f; struct dirent *e; @@ -694,7 +697,7 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist ) ctx->gen.recent += i; #ifdef USE_DB if (ctx->db) { - make_key( &key, e->d_name ); + make_key( conf->info_stop, &key, e->d_name ); if ((ret = ctx->db->get( ctx->db, 0, &key, &value, 0 ))) { if (ret != DB_NOTFOUND) { ctx->db->err( ctx->db, ret, "Maildir error: db->get()" ); @@ -834,7 +837,7 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist ) if ((u = strstr( entry->base, ",U=" ))) for (ru = u + 3; isdigit( (unsigned char)*ru ); ru++); else - u = ru = strchr( entry->base, ':' ); + u = ru = strchr( entry->base, conf->info_delimiter ); fnl = (u ? nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%.*s,U=%d%s", subdirs[entry->recent], (int)(u - entry->base), entry->base, uid, ru ) : nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%s,U=%d", subdirs[entry->recent], entry->base, uid )) @@ -906,7 +909,7 @@ maildir_init_msg( maildir_store_t *ctx, maildir_message_t *msg, msg_t *entry ) msg->gen.status |= M_RECENT; if (ctx->gen.opts & OPEN_FLAGS) { msg->gen.status |= M_FLAGS; - msg->gen.flags = maildir_parse_flags( msg->base ); + msg->gen.flags = maildir_parse_flags( ((maildir_store_conf_t *)ctx->gen.conf)->info_prefix, msg->base ); } else msg->gen.flags = 0; } @@ -1177,16 +1180,16 @@ maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data, } close( fd ); if (!(gmsg->status & M_FLAGS)) - data->flags = maildir_parse_flags( msg->base ); + data->flags = maildir_parse_flags( ((maildir_store_conf_t *)gctx->conf)->info_prefix, msg->base ); cb( DRV_OK, aux ); } static int -maildir_make_flags( int flags, char *buf ) +maildir_make_flags( char info_delimiter, int flags, char *buf ) { unsigned i, d; - buf[0] = ':'; + buf[0] = info_delimiter; buf[1] = '2'; buf[2] = ','; for (d = 3, i = 0; i < as(Flags); i++) @@ -1231,7 +1234,7 @@ maildir_store_msg( store_t *gctx, msg_data_t *data, int to_trash, box = ctx->trash; } - maildir_make_flags( data->flags, fbuf ); + maildir_make_flags( ((maildir_store_conf_t *)gctx->conf)->info_delimiter, data->flags, fbuf ); nfsnprintf( buf, sizeof(buf), "%s/tmp/%s%s", box, base, fbuf ); if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) { if (errno != ENOENT || !to_trash) { @@ -1302,6 +1305,7 @@ static void maildir_set_flags( store_t *gctx, message_t *gmsg, int uid ATTR_UNUSED, int add, int del, void (*cb)( int sts, void *aux ), void *aux ) { + maildir_store_conf_t *conf = (maildir_store_conf_t *)gctx->conf; maildir_store_t *ctx = (maildir_store_t *)gctx; maildir_message_t *msg = (maildir_message_t *)gmsg; char *s, *p; @@ -1319,7 +1323,7 @@ maildir_set_flags( store_t *gctx, message_t *gmsg, int uid ATTR_UNUSED, int add, oob(); memcpy( buf + bl, msg->base, ol + 1 ); memcpy( nbuf + bl, msg->base, ol + 1 ); - if ((s = strstr( nbuf + bl, ":2," ))) { + if ((s = strstr( nbuf + bl, conf->info_prefix ))) { s += 3; fl = ol - (s - (nbuf + bl)); for (i = 0; i < as(Flags); i++) { @@ -1337,7 +1341,7 @@ maildir_set_flags( store_t *gctx, message_t *gmsg, int uid ATTR_UNUSED, int add, } tl = ol + 3 + fl; } else { - tl = ol + maildir_make_flags( msg->gen.flags, nbuf + bl + ol ); + tl = ol + maildir_make_flags( conf->info_delimiter, msg->gen.flags, nbuf + bl + ol ); } if (!rename( buf, nbuf )) break; @@ -1362,7 +1366,7 @@ maildir_purge_msg( maildir_store_t *ctx, const char *name ) { int ret; - make_key( &key, (char *)name ); + make_key( ((maildir_store_conf_t *)ctx->gen.conf)->info_stop, &key, (char *)name ); if ((ret = ctx->db->del( ctx->db, 0, &key, 0 ))) { ctx->db->err( ctx->db, ret, "Maildir error: db->del()" ); return DRV_BOX_BAD; @@ -1384,7 +1388,7 @@ maildir_trash_msg( store_t *gctx, message_t *gmsg, for (;;) { nfsnprintf( buf, sizeof(buf), "%s/%s/%s", gctx->path, subdirs[gmsg->status & M_RECENT], msg->base ); - s = strstr( msg->base, ":2," ); + s = strstr( msg->base, ((maildir_store_conf_t *)gctx->conf)->info_prefix ); nfsnprintf( nbuf, sizeof(nbuf), "%s/%s/%ld.%d_%d.%s%s", ctx->trash, subdirs[gmsg->status & M_RECENT], (long)time( 0 ), Pid, ++MaildirCount, Hostname, s ? s : "" ); if (!rename( buf, nbuf )) @@ -1484,6 +1488,7 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep ) if (strcasecmp( "MaildirStore", cfg->cmd )) return 0; store = nfcalloc( sizeof(*store) ); + store->info_delimiter = FieldDelimiter; store->gen.driver = &maildir_driver; store->gen.name = nfstrdup( cfg->val ); @@ -1496,10 +1501,24 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep ) else if (!strcasecmp( "AltMap", cfg->cmd )) store->alt_map = parse_bool( cfg ); #endif /* USE_DB */ - else + else if (!strcasecmp( "InfoDelimiter", cfg->cmd )) { + if (strlen( cfg->val ) != 1) { + error( "%s:%d: Info delimiter must be exactly one character long\n", cfg->file, cfg->line ); + cfg->err = 1; + continue; + } + store->info_delimiter = cfg->val[0]; + if (!ispunct( store->info_delimiter )) { + error( "%s:%d: Info delimiter must be a punctuation character\n", cfg->file, cfg->line ); + cfg->err = 1; + continue; + } + } else parse_generic_store( &store->gen, cfg ); if (!store->inbox) store->inbox = expand_strdup( "~/Maildir" ); + nfasprintf( &store->info_prefix, "%c2,", store->info_delimiter ); + nfasprintf( &store->info_stop, "%c,", store->info_delimiter ); *storep = &store->gen; return 1; } diff --git a/src/main.c b/src/main.c index 3efd179..bb8fc36 100644 --- a/src/main.c +++ b/src/main.c @@ -33,6 +33,11 @@ int DFlags; int UseFSync = 1; +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__) +char FieldDelimiter = ';'; +#else +char FieldDelimiter = ':'; +#endif int Pid; /* for maildir and imap */ char Hostname[256]; /* for maildir */ diff --git a/src/mbsync.1 b/src/mbsync.1 index 16088bd..2f7e7ed 100644 --- a/src/mbsync.1 +++ b/src/mbsync.1 @@ -229,6 +229,13 @@ The location of the \fBINBOX\fR. This is \fInot\fR relative to \fBPath\fR, but it is allowed to place the \fBINBOX\fR inside the \fBPath\fR. (Default: \fI~/Maildir\fR) .. +.TP +\fBInfoDelimiter\fR \fIdelim\fR +The character used to delimit the info field from a message's basename. +The Maildir standard defines this to be the colon, but this is incompatible +with DOS/Windows file systems. +(Default: the value of \fBFieldDelimiter\fR) +.. .SS IMAP4 Accounts .TP \fBIMAPAccount\fR \fIname\fR @@ -513,7 +520,8 @@ mailbox name to make up a complete path. .br This option can be used outside any section for a global effect. In this case the appended string is made up according to the pattern -\fB:\fImaster\fB:\fImaster-box\fB_:\fIslave\fB:\fIslave-box\fR. +\fB:\fImaster\fB:\fImaster-box\fB_:\fIslave\fB:\fIslave-box\fR +(see also \fBFieldDelimiter\fR below). .br (Global default: \fI~/.mbsync/\fR). .. @@ -549,6 +557,16 @@ in particular modern systems like ext4, btrfs and xfs. The performance impact on older file systems may be disproportionate. (Default: \fIyes\fR) .. +.TP +\fBFieldDelimiter\fR \fIdelim\fR +The character to use to delimit fields in the string appended to a global +\fBSyncState\fR. +\fBmbsync\fR prefers to use the colon, but this is incompatible with +DOS/Windows file systems. +This option is meaningless for \fBSyncState\fR if the latter is \fB*\fR, +obviously. However, it also determines the default of \fBInfoDelimiter\fR. +(Global default: \fI;\fR on Windows, \fI:\fR everywhere else) +.. .SH RECOMMENDATIONS Make sure your IMAP server does not auto-expunge deleted messages - it is slow, and semantically somewhat questionable. Specifically, Gmail needs to diff --git a/src/sync.c b/src/sync.c index d8412fd..4a4c913 100644 --- a/src/sync.c +++ b/src/sync.c @@ -669,9 +669,10 @@ box_selected( int sts, void *aux ) if (chan->sync_state) nfasprintf( &svars->dname, "%s%s", chan->sync_state, csname ); else { + char c = FieldDelimiter; cmname = clean_strdup( svars->box_name[M] ); - nfasprintf( &svars->dname, "%s:%s:%s_:%s:%s", global_conf.sync_state, - chan->stores[M]->name, cmname, chan->stores[S]->name, csname ); + nfasprintf( &svars->dname, "%s%c%s%c%s_%c%s%c%s", global_conf.sync_state, + c, chan->stores[M]->name, c, cmname, c, chan->stores[S]->name, c, csname ); free( cmname ); } free( csname );