Hi Oswald,
> > It might be worthwhile to set `cfile->error` here too, and
> > I'll gladly amend the patch if you agree.
>
> yeah, i'd do that.
Done!
> theoretically yes, though in practice i'd recommend that users just call
> a script, because the extra layer of quoting/escaping makes things ugly
> and easy to get wrong. add it if you can't help yourself. ;)
> so just call a script already! ;)
Oh you should see my personal configuration. So many inline scripts
under multiple layers of escaping. But, with my own troubles in getting
a clean implementation plus the feedback, I think I can help myself ;)
External scripts it is.
> eval => included, i guess.
> also, that space afterwards should be probably a colon, otherwise it's
> kinda confusing and weird-looking.
Changed it, but are you sure? Now that the command is called `Eval`? The
space *was* weird-looking, thanks. I was already unhappy about it.
> on that matter, have you tested all error scenarios?
The ones I added? Or all that were modified? On the ones that I added,
yes, except for failure of popen and the `ret < 0` case in `eval_pclose`.
I have to admit I do not really know how to trigger those.
> > +void
> > +eval_popen( conffile_t *cfile, const char *cmd )
>
> should return an int (zero is error), which should lead to early
> termination.
It was missing the `cfile->err` plumbing. Which is fixed, assuming that's
what you meant, in the attached implementation. I could also return 0 from
getcline in that case, but that does not match the rest of the parser. Is
a failed popen that different?
> ... but here it gets "interesting":
> seeing the command after dequoting may be actually helpful, especially
> when multi-line commands are supported. though of course this can be
> ridiculously long. but i guess we'll get away with just qouting it.
Added this.
> > +char *
> > +read_cline( conffile_t *cfile )
>
> i think that should be just an int.
Fixed (?) by comparing it to `NULL` when returning. Also made it less
convoluted.
> > +.SS Dynamic options
>
> why a separate section? it doesn't really fit the pattern for the *Cmd
> commands.
I didn't really know where to put it. It can occur literally everywhere,
which no other options (like the `*Cmd`) do, which is why I did not add
it there. This time I put it next to the global options.
> >+void
> >+conf_error( const conffile_t *cfile, const char* fmt, ... )
>
> second asterisk has space on the wrong side.
> this repeats for several other string arguments.
> but i think this is actually the only whitespace mistake you've made,
> which may be a first among non-trivial external isync contributions
> during my "reign". ^^
Hehehe :) Nice trophy. I don't suppose you have some formatter config
file?
(Hopefully) All your other proposed changes were incorporated in these
revised patches.
Cheers!
>From 92cc071aeb14cab302eb0db206a30e2c5c78623b Mon Sep 17 00:00:00 2001
From: Michiel van den Heuvel <michielvdnheu...@gmail.com>
Date: Thu, 10 Aug 2023 05:20:55 +0200
Subject: [PATCH 1/2] Refactor printing configuration errors
In preparation for adding the output of commands as configuration lines,
which will complicate printing.
---
src/config.c | 106 +++++++++++++++++++++-------------------------
src/config.h | 3 ++
src/driver.c | 6 +--
src/drv_imap.c | 46 +++++++-------------
src/drv_maildir.c | 9 ++--
5 files changed, 72 insertions(+), 98 deletions(-)
diff --git a/src/config.c b/src/config.c
index 456bd47..0de2cb2 100644
--- a/src/config.c
+++ b/src/config.c
@@ -61,6 +61,31 @@ expand_strdup( const char *s, const conffile_t *cfile )
}
}
+void
+conf_error( conffile_t *cfile, const char *fmt, ... )
+{
+ va_list va;
+
+ flushn();
+ fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
+ va_start( va, fmt );
+ vfprintf( stderr, fmt, va );
+ va_end( va );
+ cfile->err = 1;
+}
+
+void
+conf_sys_error( conffile_t *cfile, const char *fmt, ... )
+{
+ va_list va;
+
+ fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
+ va_start( va, fmt );
+ vsys_error( fmt, va );
+ va_end( va );
+ cfile->err = 1;
+}
+
char *
get_arg( conffile_t *cfile, int required, int *comment )
{
@@ -76,8 +101,7 @@ get_arg( conffile_t *cfile, int required, int *comment )
if (comment)
*comment = (c == '#');
if (required) {
- error( "%s:%d: parameter missing\n", cfile->file, cfile->line );
- cfile->err = 1;
+ conf_error( cfile, "parameter missing\n" );
}
ret = NULL;
} else {
@@ -98,13 +122,11 @@ get_arg( conffile_t *cfile, int required, int *comment )
}
*t = 0;
if (escaped) {
- error( "%s:%d: unterminated escape sequence\n", cfile->file, cfile->line );
- cfile->err = 1;
+ conf_error( cfile, "unterminated escape sequence\n" );
ret = NULL;
}
if (quoted) {
- error( "%s:%d: missing closing quote\n", cfile->file, cfile->line );
- cfile->err = 1;
+ conf_error( cfile, "missing closing quote\n" );
ret = NULL;
}
}
@@ -124,9 +146,7 @@ parse_bool( conffile_t *cfile )
strcasecmp( cfile->val, "false" ) &&
strcasecmp( cfile->val, "off" ) &&
strcmp( cfile->val, "0" )) {
- error( "%s:%d: invalid boolean value '%s'\n",
- cfile->file, cfile->line, cfile->val );
- cfile->err = 1;
+ conf_error( cfile, "invalid boolean value '%s'\n", cfile->val );
}
return 0;
}
@@ -139,9 +159,7 @@ parse_int( conffile_t *cfile )
ret = strtol( cfile->val, &p, 10 );
if (*p) {
- error( "%s:%d: invalid integer value '%s'\n",
- cfile->file, cfile->line, cfile->val );
- cfile->err = 1;
+ conf_error( cfile, "invalid integer value '%s'\n", cfile->val );
return 0;
}
return ret;
@@ -161,9 +179,7 @@ parse_size( conffile_t *cfile )
if (*p == 'b' || *p == 'B')
p++;
if (*p) {
- fprintf (stderr, "%s:%d: invalid size '%s'\n",
- cfile->file, cfile->line, cfile->val);
- cfile->err = 1;
+ conf_error( cfile, "invalid size '%s'\n", cfile->val);
return 0;
}
return ret;
@@ -249,9 +265,7 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
} else if (!strcasecmp( "None", arg ) || !strcasecmp( "Noop", arg )) {
conf->ops[F] |= XOP_TYPE_NOOP;
} else {
- error( "%s:%d: invalid Sync arg '%s'\n",
- cfile->file, cfile->line, arg );
- cfile->err = 1;
+ conf_error( cfile, "invalid Sync arg '%s'\n", arg );
}
} while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL )));
conf->ops[F] |= XOP_HAVE_TYPE;
@@ -268,9 +282,7 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
} else if (!strcasecmp( "Near", arg )) {
conf->expire_side = N;
} else {
- error( "%s:%d: invalid ExpireSide argument '%s'\n",
- cfile->file, cfile->line, arg );
- cfile->err = 1;
+ conf_error( cfile, "invalid ExpireSide argument '%s'\n", arg );
}
} else if (!strcasecmp( "ExpireUnread", cfile->cmd )) {
conf->expire_unread = parse_bool( cfile );
@@ -295,9 +307,7 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
} else if (!strcasecmp( "None", arg )) {
conf->ops[F] |= op * (XOP_EXPUNGE_NOOP / OP_EXPUNGE);
} else {
- error( "%s:%d: invalid %s arg '%s'\n",
- cfile->file, cfile->line, boxOps[i].name, arg );
- cfile->err = 1;
+ conf_error( cfile, "invalid %s arg '%s'\n", boxOps[i].name, arg );
}
} while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL )));
conf->ops[F] |= op * (XOP_HAVE_EXPUNGE / OP_EXPUNGE);
@@ -315,10 +325,8 @@ getcline( conffile_t *cfile )
char *arg;
int comment;
- if (cfile->rest && (arg = get_arg( cfile, ARG_OPTIONAL, NULL ))) {
- error( "%s:%d: excess token '%s'\n", cfile->file, cfile->line, arg );
- cfile->err = 1;
- }
+ if (cfile->rest && (arg = get_arg( cfile, ARG_OPTIONAL, NULL )))
+ conf_error( cfile, "excess token '%s'\n", arg );
while (fgets( cfile->buf, cfile->bufl, cfile->fp )) {
cfile->line++;
cfile->rest = cfile->buf;
@@ -546,9 +554,7 @@ load_config( const char *where )
cfile.ms_warn = 1;
linkst:
if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) {
- error( "%s:%d: malformed mailbox spec\n",
- cfile.file, cfile.line );
- cfile.err = 1;
+ conf_error( &cfile, "malformed mailbox spec\n" );
continue;
}
*p = 0;
@@ -559,18 +565,14 @@ load_config( const char *where )
}
}
channel->stores[fn] = (void *)~0;
- error( "%s:%d: unknown store '%s'\n",
- cfile.file, cfile.line, cfile.val + 1 );
- cfile.err = 1;
+ conf_error( &cfile, "unknown store '%s'\n", cfile.val + 1 );
continue;
stpcom:
if (*++p)
channel->boxes[fn] = nfstrdup( p );
} else if (!getopt_helper( &cfile, &cops, channel )) {
- error( "%s:%d: keyword '%s' is not recognized in Channel sections\n",
- cfile.file, cfile.line, cfile.cmd );
+ conf_error( &cfile, "keyword '%s' is not recognized in Channel sections\n", cfile.cmd );
cfile.rest = NULL;
- cfile.err = 1;
}
}
if (!channel->stores[F]) {
@@ -615,10 +617,8 @@ load_config( const char *where )
arg = cfile.val;
goto addone;
} else {
- error( "%s:%d: keyword '%s' is not recognized in Group sections\n",
- cfile.file, cfile.line, cfile.cmd );
+ conf_error( &cfile, "keyword '%s' is not recognized in Group sections\n", cfile.cmd );
cfile.rest = NULL;
- cfile.err = 1;
}
}
glob_ok = 0;
@@ -627,36 +627,26 @@ load_config( const char *where )
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;
+ conf_error( &cfile, "Field delimiter must be exactly one character long\n" );
} 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;
- }
+ if (!ispunct( FieldDelimiter ))
+ conf_error( &cfile, "Field delimiter must be a punctuation character\n" );
}
} else if (!strcasecmp( "BufferLimit", cfile.cmd )) {
BufferLimit = parse_size( &cfile );
- if (!BufferLimit) {
- error( "%s:%d: BufferLimit cannot be zero\n", cfile.file, cfile.line );
- cfile.err = 1;
- }
+ if (!BufferLimit)
+ conf_error( &cfile, "BufferLimit cannot be zero\n" );
} else if (!getopt_helper( &cfile, &gcops, &global_conf )) {
- error( "%s:%d: '%s' is not a recognized section-starting or global keyword\n",
- cfile.file, cfile.line, cfile.cmd );
- cfile.err = 1;
+ conf_error( &cfile, "'%s' is not a recognized section-starting or global keyword\n", cfile.cmd );
cfile.rest = NULL;
while (getcline( &cfile ))
if (!cfile.cmd)
goto reloop;
break;
}
- if (!glob_ok) {
- error( "%s:%d: global options may not follow sections\n",
- cfile.file, cfile.line );
- cfile.err = 1;
- }
+ if (!glob_ok)
+ conf_error( &cfile, "global options may not follow sections\n" );
}
fclose (cfile.fp);
if (cfile.ms_warn)
diff --git a/src/config.h b/src/config.h
index 0762b58..cbce8f4 100644
--- a/src/config.h
+++ b/src/config.h
@@ -29,6 +29,9 @@ extern char FieldDelimiter;
char *expand_strdup( const char *s, const conffile_t *cfile );
+void ATTR_PRINTFLIKE(2, 3) conf_error( conffile_t *, const char *, ... );
+void ATTR_PRINTFLIKE(2, 3) conf_sys_error( conffile_t *, const char *, ... );
+
char *get_arg( conffile_t *cfile, int required, int *comment );
char parse_bool( conffile_t *cfile );
diff --git a/src/driver.c b/src/driver.c
index 6b88dbb..885d7a3 100644
--- a/src/driver.c
+++ b/src/driver.c
@@ -83,15 +83,13 @@ parse_generic_store( store_conf_t *store, conffile_t *cfg, const char *type )
const char *p;
for (p = cfg->val; *p; p++) {
if (*p == '/') {
- error( "%s:%d: flattened hierarchy delimiter cannot contain the canonical delimiter '/'\n", cfg->file, cfg->line );
- cfg->err = 1;
+ conf_error( cfg, "flattened hierarchy delimiter cannot contain the canonical delimiter '/'\n" );
return;
}
}
store->flat_delim = nfstrdup( cfg->val );
} else {
- error( "%s:%d: keyword '%s' is not recognized in %s sections\n", cfg->file, cfg->line, cfg->cmd, type );
+ conf_error( cfg, "keyword '%s' is not recognized in %s sections\n", cfg->cmd, type );
cfg->rest = NULL;
- cfg->err = 1;
}
}
diff --git a/src/drv_imap.c b/src/drv_imap.c
index ad95e3d..a0b2305 100644
--- a/src/drv_imap.c
+++ b/src/drv_imap.c
@@ -3782,8 +3782,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
} else if (!strcasecmp( "Port", cfg->cmd )) {
int port = parse_int( cfg );
if ((unsigned)port > 0xffff) {
- error( "%s:%d: Invalid port number\n", cfg->file, cfg->line );
- cfg->err = 1;
+ conf_error( cfg, "Invalid port number\n" );
} else {
server->sconf.port = (ushort)port;
}
@@ -3791,8 +3790,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
server->sconf.timeout = parse_int( cfg ) * 1000;
} else if (!strcasecmp( "PipelineDepth", cfg->cmd )) {
if ((server->max_in_progress = parse_int( cfg )) < 1) {
- error( "%s:%d: PipelineDepth must be at least 1\n", cfg->file, cfg->line );
- cfg->err = 1;
+ conf_error( cfg, "PipelineDepth must be at least 1\n" );
}
} else if (!strcasecmp( "DisableExtension", cfg->cmd ) ||
!strcasecmp( "DisableExtensions", cfg->cmd )) {
@@ -3804,33 +3802,29 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
goto gotcap;
}
}
- error( "%s:%d: Unrecognized IMAP extension '%s'\n", cfg->file, cfg->line, arg );
- cfg->err = 1;
+ conf_error( cfg, "Unrecognized IMAP extension '%s'\n", arg );
gotcap: ;
} 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, cfg );
if (access( server->sconf.cert_file, R_OK )) {
- sys_error( "%s:%d: CertificateFile '%s'",
- cfg->file, cfg->line, server->sconf.cert_file );
- cfg->err = 1;
+ conf_sys_error( cfg, "CertificateFile '%s'",
+ server->sconf.cert_file );
}
} 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, 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;
+ conf_sys_error( cfg, "ClientCertificate '%s'",
+ server->sconf.client_certfile );
}
} else if (!strcasecmp( "ClientKey", cfg->cmd )) {
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 );
- cfg->err = 1;
+ conf_sys_error( cfg, "ClientKey '%s'",
+ server->sconf.client_keyfile );
}
} else if (!strcasecmp( "CipherString", cfg->cmd )) {
server->sconf.cipher_string = nfstrdup( cfg->val );
@@ -3843,8 +3837,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
} else if (*arg == '-') {
and_mask = ~0;
} else {
- error( "%s:%d: TLSVersions arguments must start with +/-\n", cfg->file, cfg->line );
- cfg->err = 1;
+ conf_error( cfg, "TLSVersions arguments must start with +/-\n" );
continue;
}
arg++;
@@ -3857,8 +3850,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
} else if (!strcmp( "1.3", arg )) {
val = TLSv1_3;
} else {
- error( "%s:%d: Unrecognized TLS version '%s'\n", cfg->file, cfg->line, arg );
- cfg->err = 1;
+ conf_error( cfg, "Unrecognized TLS version '%s'\n", arg );
continue;
}
or_mask &= val;
@@ -3888,8 +3880,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
} else if (!strcasecmp( "TLSv1.3", arg )) {
server->sconf.ssl_versions |= TLSv1_3;
} else {
- error( "%s:%d: Unrecognized SSL version\n", cfg->file, cfg->line );
- cfg->err = 1;
+ conf_error( cfg, "Unrecognized SSL version\n" );
}
} while ((arg = get_arg( cfg, ARG_OPTIONAL, NULL )));
#else
@@ -3927,8 +3918,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
cfg->err = 1;
#endif
} else {
- error( "%s:%d: Invalid TLS type\n", cfg->file, cfg->line );
- cfg->err = 1;
+ conf_error( cfg, "Invalid TLS type\n" );
}
} else if (!strcasecmp( "AuthMech", cfg->cmd ) ||
!strcasecmp( "AuthMechs", cfg->cmd )) {
@@ -3946,8 +3936,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
if (srv->name && !strcmp( srv->name, cfg->val ))
goto gotsrv;
store->server = (void *)~0;
- error( "%s:%d: unknown IMAP account '%s'\n", cfg->file, cfg->line, cfg->val );
- cfg->err = 1;
+ conf_error( cfg, "unknown IMAP account '%s'\n", cfg->val );
continue;
gotsrv:
store->server = srv;
@@ -3959,8 +3948,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
store->path = nfstrdup( cfg->val );
} else if (!strcasecmp( "PathDelimiter", cfg->cmd )) {
if (strlen( cfg->val ) != 1) {
- error( "%s:%d: Path delimiter must be exactly one character long\n", cfg->file, cfg->line );
- cfg->err = 1;
+ conf_error( cfg, "Path delimiter must be exactly one character long\n" );
continue;
}
store->delimiter = cfg->val[0];
@@ -3969,10 +3957,8 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
}
continue;
} else {
- error( "%s:%d: keyword '%s' is not recognized in IMAPAccount sections\n",
- cfg->file, cfg->line, cfg->cmd );
+ conf_error( cfg, "keyword '%s' is not recognized in IMAPAccount sections\n", cfg->cmd );
cfg->rest = NULL;
- cfg->err = 1;
continue;
}
acc_opt = 1;
diff --git a/src/drv_maildir.c b/src/drv_maildir.c
index cdffc32..3ce9cce 100644
--- a/src/drv_maildir.c
+++ b/src/drv_maildir.c
@@ -1899,14 +1899,12 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep )
#endif /* USE_DB */
} 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;
+ conf_error( cfg, "Info delimiter must be exactly one character long\n" );
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;
+ conf_error( cfg, "Info delimiter must be a punctuation character\n" );
continue;
}
} else if (!strcasecmp( "SubFolders", cfg->cmd )) {
@@ -1917,8 +1915,7 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep )
} else if (!strcasecmp( "Legacy", cfg->val )) {
store->sub_style = SUB_LEGACY;
} else {
- error( "%s:%d: Unrecognized SubFolders style\n", cfg->file, cfg->line );
- cfg->err = 1;
+ conf_error( cfg, "Unrecognized SubFolders style\n" );
}
} else {
parse_generic_store( &store->gen, cfg, "MaildirStore" );
--
2.41.0
>From 5f1f3ebd8192c7d325cf7fa8fb50914ec0fe423b Mon Sep 17 00:00:00 2001
From: Michiel van den Heuvel <michielvdnheu...@gmail.com>
Date: Thu, 17 Aug 2023 20:25:51 +0200
Subject: [PATCH 2/2] Add Eval directive to config parser
---
src/config.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++---
src/config.h | 3 +++
src/mbsync.1 | 6 +++++
3 files changed, 76 insertions(+), 4 deletions(-)
diff --git a/src/config.c b/src/config.c
index 0de2cb2..09f1fec 100644
--- a/src/config.c
+++ b/src/config.c
@@ -61,13 +61,22 @@ expand_strdup( const char *s, const conffile_t *cfile )
}
}
+static void
+conf_print_loc( const conffile_t *cfile )
+{
+ if (cfile->eval_fp)
+ fprintf( stderr, "%s:%d:included:%d: ", cfile->file, cfile->line, cfile->eval_line );
+ else
+ fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
+}
+
void
conf_error( conffile_t *cfile, const char *fmt, ... )
{
va_list va;
flushn();
- fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
+ conf_print_loc( cfile );
va_start( va, fmt );
vfprintf( stderr, fmt, va );
va_end( va );
@@ -79,7 +88,7 @@ conf_sys_error( conffile_t *cfile, const char *fmt, ... )
{
va_list va;
- fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
+ conf_print_loc( cfile );
va_start( va, fmt );
vsys_error( fmt, va );
va_end( va );
@@ -319,6 +328,50 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
return 1;
}
+static void
+eval_popen( conffile_t *cfile, const char *cmd )
+{
+ if (!(cfile->eval_fp = popen( cmd, "r" ))) {
+ sys_error( "popen failed" );
+ cfile->err = 1;
+ }
+ cfile->eval_line = 0;
+ cfile->eval_command = nfstrdup( cmd );
+}
+
+static void
+eval_pclose( conffile_t *cfile )
+{
+ int ret;
+
+ ret = pclose( cfile->eval_fp );
+ cfile->eval_fp = NULL;
+ if (ret) {
+ if (ret < 0)
+ conf_sys_error( cfile, "command failed: %s", cfile->eval_command );
+ else if (WIFSIGNALED( ret ))
+ conf_error( cfile, "command crashed: %s\n", cfile->eval_command );
+ else
+ conf_error( cfile, "command exited with status %d: %s\n",
+ WEXITSTATUS( ret ), cfile->eval_command );
+ }
+ free( cfile->eval_command );
+}
+
+static int
+read_cline( conffile_t *cfile )
+{
+ if (cfile->eval_fp) {
+ cfile->eval_line++;
+ if (fgets( cfile->buf, cfile->bufl, cfile->eval_fp ))
+ return 1;
+ else
+ eval_pclose( cfile );
+ }
+ cfile->line++;
+ return fgets( cfile->buf, cfile->bufl, cfile->fp ) != NULL;
+}
+
int
getcline( conffile_t *cfile )
{
@@ -327,14 +380,23 @@ getcline( conffile_t *cfile )
if (cfile->rest && (arg = get_arg( cfile, ARG_OPTIONAL, NULL )))
conf_error( cfile, "excess token '%s'\n", arg );
- while (fgets( cfile->buf, cfile->bufl, cfile->fp )) {
- cfile->line++;
+ while (read_cline( cfile )) {
cfile->rest = cfile->buf;
if (!(cfile->cmd = get_arg( cfile, ARG_OPTIONAL, &comment ))) {
if (comment)
continue;
return 1;
}
+ if (!strcmp( cfile->cmd, "Eval" )) {
+ if ((arg = get_arg( cfile, ARG_REQUIRED, NULL ))) {
+ if (cfile->eval_fp) {
+ conf_error( cfile, "nested eval\n" );
+ continue;
+ }
+ eval_popen( cfile, arg );
+ }
+ continue;
+ }
if (!(cfile->val = get_arg( cfile, ARG_REQUIRED, NULL )))
continue;
return 1;
@@ -489,6 +551,7 @@ load_config( const char *where )
return 1;
}
buf[sizeof(buf) - 1] = 0;
+ cfile.eval_fp = NULL;
cfile.buf = buf;
cfile.bufl = sizeof(buf) - 1;
cfile.line = 0;
diff --git a/src/config.h b/src/config.h
index cbce8f4..be08a5c 100644
--- a/src/config.h
+++ b/src/config.h
@@ -12,10 +12,13 @@
typedef struct {
const char *file;
+ char *eval_command;
FILE *fp;
+ FILE *eval_fp;
char *buf;
int bufl;
int line;
+ int eval_line;
int err;
int ms_warn, renew_warn, delete_warn;
int path_len;
diff --git a/src/mbsync.1 b/src/mbsync.1
index 939c8c5..f32218b 100644
--- a/src/mbsync.1
+++ b/src/mbsync.1
@@ -786,6 +786,12 @@ absolute limit, as even a single message can consume more memory than
this.
(Default: \fI10M\fR)
.
+.TP
+\fBEval\fR \fIcommand\fR
+Specify a shell command to obtain options. The command can output zero
+or more lines which will be interpreted as if they appeared inline. This
+keyword is allowed in every section.
+.
.SH CONSOLE OUTPUT
If \fBmbsync\fR's output is connected to a console, it will print progress
counters by default. The output will look like this:
--
2.41.0
_______________________________________________
isync-devel mailing list
isync-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/isync-devel