From d3f118be79686adfcd085211ad3a4b3fa892bf4e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 30 May 2022 23:04:52 +0200 Subject: [PATCH] re-interpret relative local paths in config file this makes config+data file "sets" relocatable, which is useful for testing. this is technically a gratuitous backwards incompatible behavior change, but to the degree that anyone uses relative paths at all, they almost certainly rely on PWD being set up such that they won't see a difference. --- NEWS | 3 +++ src/config.c | 25 +++++++++++++++++++------ src/config.h | 3 ++- src/drv_imap.c | 6 +++--- src/drv_maildir.c | 6 +++--- src/mbsync.1 | 5 ++++- src/run-tests.pl | 8 ++++---- src/sync.h | 2 +- 8 files changed, 39 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index 5513385..aa53a19 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,9 @@ Changed default config & state locations to follow the XDG basedir spec. The old locations remain supported. +The reference point for relative local paths in the configuration file +is now the file's containing directory. + [1.4.0] The 'isync' compatibility wrapper was removed. diff --git a/src/config.c b/src/config.c index 65e080f..880f027 100644 --- a/src/config.c +++ b/src/config.c @@ -23,7 +23,7 @@ char FieldDelimiter = ':'; static store_conf_t *stores; char * -expand_strdup( const char *s ) +expand_strdup( const char *s, const conffile_t *cfile ) { struct passwd *pw; const char *p, *q; @@ -51,6 +51,9 @@ expand_strdup( const char *s ) } nfasprintf( &r, "%s%s", q, p ? p : "" ); return r; + } else if (*s != '/') { + nfasprintf( &r, "%.*s%s", cfile->path_len, cfile->file, s ); + return r; } else { return nfstrdup( s ); } @@ -220,7 +223,7 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf ) } while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL ))); conf->ops[F] |= XOP_HAVE_TYPE; } else if (!strcasecmp( "SyncState", cfile->cmd )) { - conf->sync_state = expand_strdup( cfile->val ); + conf->sync_state = !strcmp( cfile->val, "*" ) ? "*" : expand_strdup( cfile->val, cfile ); } else if (!strcasecmp( "CopyArrivalDate", cfile->cmd )) { conf->use_internal_date = parse_bool( cfile ); } else if (!strcasecmp( "MaxMessages", cfile->cmd )) { @@ -354,24 +357,34 @@ load_config( const char *where ) char buf[1024]; if (!where) { + int path_len, path_len2; const char *config_home = getenv( "XDG_CONFIG_HOME" ); if (config_home) - nfsnprintf( path, sizeof(path), "%s/isyncrc", config_home ); + nfsnprintf( path, sizeof(path), "%s/%nisyncrc", config_home, &path_len ); else - nfsnprintf( path, sizeof(path), "%s/.config/isyncrc", Home ); - nfsnprintf( path2, sizeof(path2), "%s/.mbsyncrc", Home ); + nfsnprintf( path, sizeof(path), "%s/.config/%nisyncrc", Home, &path_len ); + nfsnprintf( path2, sizeof(path2), "%s/%n.mbsyncrc", Home, &path_len2 ); struct stat st; int ex = !lstat( path, &st ); int ex2 = !lstat( path2, &st ); if (ex2 && !ex) { cfile.file = path2; + cfile.path_len = path_len2; } else { if (ex && ex2) warn( "Both %s and %s exist; using the former.\n", path, path2 ); cfile.file = path; + cfile.path_len = path_len; } } else { - cfile.file = where; + const char *sl = strrchr( where, '/' ); + if (!sl) { + nfsnprintf( path, sizeof(path), "./%n%s", &cfile.path_len, where ); + cfile.file = path; + } else { + cfile.path_len = sl - where + 1; + cfile.file = where; + } } info( "Reading configuration file %s\n", cfile.file ); diff --git a/src/config.h b/src/config.h index 063c128..bc25511 100644 --- a/src/config.h +++ b/src/config.h @@ -18,6 +18,7 @@ typedef struct { int line; int err; int ms_warn; + int path_len; char *cmd, *val, *rest; } conffile_t; @@ -26,7 +27,7 @@ extern char FieldDelimiter; #define ARG_OPTIONAL 0 #define ARG_REQUIRED 1 -char *expand_strdup( const char *s ); +char *expand_strdup( const char *s, const conffile_t *cfile ); char *get_arg( conffile_t *cfile, int required, int *comment ); diff --git a/src/drv_imap.c b/src/drv_imap.c index 4ac00ec..34cc951 100644 --- a/src/drv_imap.c +++ b/src/drv_imap.c @@ -3590,7 +3590,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep ) } while ((arg = get_arg( cfg, ARG_OPTIONAL, NULL ))); #ifdef HAVE_LIBSSL } else if (!strcasecmp( "CertificateFile", cfg->cmd )) { - server->sconf.cert_file = expand_strdup( cfg->val ); + server->sconf.cert_file = expand_strdup( cfg->val, cfg ); if (access( server->sconf.cert_file, R_OK )) { sys_error( "%s:%d: CertificateFile '%s'", cfg->file, cfg->line, server->sconf.cert_file ); @@ -3599,14 +3599,14 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep ) } else if (!strcasecmp( "SystemCertificates", cfg->cmd )) { server->sconf.system_certs = parse_bool( cfg ); } else if (!strcasecmp( "ClientCertificate", cfg->cmd )) { - server->sconf.client_certfile = expand_strdup( cfg->val ); + server->sconf.client_certfile = expand_strdup( cfg->val, cfg ); if (access( server->sconf.client_certfile, R_OK )) { sys_error( "%s:%d: ClientCertificate '%s'", cfg->file, cfg->line, server->sconf.client_certfile ); cfg->err = 1; } } else if (!strcasecmp( "ClientKey", cfg->cmd )) { - server->sconf.client_keyfile = expand_strdup( cfg->val ); + server->sconf.client_keyfile = expand_strdup( cfg->val, cfg ); if (access( server->sconf.client_keyfile, R_OK )) { sys_error( "%s:%d: ClientKey '%s'", cfg->file, cfg->line, server->sconf.client_keyfile ); diff --git a/src/drv_maildir.c b/src/drv_maildir.c index 6c1daa4..c1de4dc 100644 --- a/src/drv_maildir.c +++ b/src/drv_maildir.c @@ -1857,9 +1857,9 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep ) while (getcline( cfg ) && cfg->cmd) { if (!strcasecmp( "Inbox", cfg->cmd )) { - store->inbox = expand_strdup( cfg->val ); + store->inbox = expand_strdup( cfg->val, cfg ); } else if (!strcasecmp( "Path", cfg->cmd )) { - store->path = expand_strdup( cfg->val ); + store->path = expand_strdup( cfg->val, cfg ); #ifdef USE_DB } else if (!strcasecmp( "AltMap", cfg->cmd )) { store->alt_map = parse_bool( cfg ); @@ -1892,7 +1892,7 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep ) } } if (!store->inbox) - store->inbox = expand_strdup( "~/Maildir" ); + store->inbox = expand_strdup( "~/Maildir", NULL ); if (store->sub_style == SUB_MAILDIRPP && store->path) { error( "Maildir store '%s': Setting Path is incompatible with 'SubFolders Maildir++'\n", store->name ); cfg->err = 1; diff --git a/src/mbsync.1 b/src/mbsync.1 index fe08ccf..403b2ce 100644 --- a/src/mbsync.1 +++ b/src/mbsync.1 @@ -105,6 +105,8 @@ and literal double quotes and backslashes (\fB\\\fR) must be backslash-escaped. All keywords (including those used as arguments) are case-insensitive. Bash-like home directory expansion using the tilde (\fB~\fR) is supported in all options which represent local paths. +The reference point for relative local paths is the configuration file's +containing directory. There are a few global options, the others apply to particular sections. Sections begin with a section-starting keyword and are terminated by an empty line or end of file. @@ -205,7 +207,8 @@ yet all messages are archived. (Default: \fBno\fR) . .SS Maildir Stores -The reference point for relative \fBPath\fRs is the current working directory. +The reference point for relative \fBPath\fRs is the configuration file's +containing directory. .P As \fBmbsync\fR needs UIDs, but no standardized UID storage scheme exists for Maildir, \fBmbsync\fR supports two schemes, each with its pros and cons. diff --git a/src/run-tests.pl b/src/run-tests.pl index d771ebd..2477826 100755 --- a/src/run-tests.pl +++ b/src/run-tests.pl @@ -289,12 +289,12 @@ sub writecfg($) "FSync no MaildirStore far -Path ./ -Inbox ./far +Path \"\" +Inbox far ".$$sfx[0]." MaildirStore near -Path ./ -Inbox ./near +Path \"\" +Inbox near ".$$sfx[1]." Channel test Far :far: diff --git a/src/sync.h b/src/sync.h index dbcbede..d091609 100644 --- a/src/sync.h +++ b/src/sync.h @@ -35,7 +35,7 @@ typedef struct channel_conf { const char *name; store_conf_t *stores[2]; const char *boxes[2]; - char *sync_state; + const char *sync_state; string_list_t *patterns; int ops[2]; int max_messages; // For near side only.