Re: [PATCH] Implementing IncludeCmd (feature request #17)

2023-08-31 Thread Oswald Buddenhagen

On Thu, Aug 31, 2023 at 10:58:27AM +0200, Michiel van den Heuvel wrote:

so if you think it would be a good use of *your* time, then go ahead.


After some somewhat more synchronous communication, it might be.


are you asking for a chat channel?
i'm @ossi:kde.org on matrix, ossi on libera.chat irc (if the matrix 
bridge feels like working), and @ossilator on various social media and 
chat sites.
i could create an official project channel (on matrix?), but 
historically it seems a bit ... superfluous.



On the subject of more things I could do, is there a way to reproduce
the 2 regressions?

congrats, you forced me to actually research what the regressions are.  
*grumble* :-D


for the imap thing, i suspect any mail with a large enough attachment 
will do. maybe the imap compress extension needs to be in use, and/or 
tls. no idea.

(possibly) related threads:
https://sourceforge.net/p/isync/mailman/isync-devel/thread/Y00lhIm7VdrJzG/D%40ugly/#msg37721793
https://sourceforge.net/p/isync/mailman/isync-devel/thread/87h740x2xe.fsf%40wavexx.thregr.org/#msg37675548
https://sourceforge.net/p/isync/mailman/isync-devel/thread/87edk45p9o.fsf%40b3l.xyz/#msg37883904

for the other problem, there is 
https://sourceforge.net/p/isync/mailman/isync-devel/thread/f4d61b60-7a93-6b78-90d1-b96b285caa9c%40quaternum.net/#msg37708463 
which contains a speculative analysis.


there is https://sourceforge.net/p/isync/bugs/69/ which may be actually 
the same thing (sounds similar, but i'm not going to think about it at 
this time of day). it has a patch attached, which is certainly 
under-tested, and needs a sanity check, possibly resulting in a complete 
rewrite.


speaking of tested, if you _really_ wanted to go overboard, you could 
add a proper unit testing suite. so far only the core syncing algo is 
tested, in a way that is really an integration test (which has the 
advantage of testing a bit more as a side effect, but it's slow and 
inelegant). this would also nicely fit with formalizing the error 
reporting.



Subject: [PATCH] Add IncludeCmd directive to config parser

+static char *
+check_excess_tokens( conffile_t *cfile )
+{
+   char *arg = NULL;
+

i generally prefer early returns (and even goto) over temporaries.


int
getcline( conffile_t *cfile )
{
...
+   else if ((arg = check_excess_tokens( cfile )))
+   conf_error( cfile, "excess token '%s', not executing 
"
+   "potentially malformed command\n", 
arg );

my thinking was that the error message would be printed already by the 
check function (hence "check", not "get"). i don't think the extra 
verbosity about the specific consequence adds all that much value.

then the check function can just return an int.

oh, one thing i totally failed to notice: this is a noteworthy new 
feature, so it should get an entry in NEWS. something like "Added 
support for scripting arbitrary parts of the config file.", before the 
currently last two entries, i think.


and you should add yourself to the end of the main contributor list in 
AUTHORS.


regards


___
isync-devel mailing list
isync-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/isync-devel


Re: [PATCH] Implementing IncludeCmd (feature request #17)

2023-08-31 Thread Michiel van den Heuvel
> that depends on whose perspective you're assuming here.
> i'm likely not going to get it done anytime soon. pushing patches on me 
> prompts me into action, so it's good for the project (not so much for my 
> other projects, but the trade-off isn't that bad, as i'm actually much 
> more efficient at reviewing than hacking myself).
> so if you think it would be a good use of *your* time, then go ahead.

After some somewhat more synchronous communication, it might be. This
project has been a nice refresher course on C.

> i implied that you need to do that anyway to handle things properly.  
> also note that you'd save the duplicated check for the command.
> 
> but looking at it again, duplication probably isn't even necessary: you 
> can factor out check_excess_tokens() and make it also return a value.

I hope this is what you meant. 

> the first patch is now Perfect (TM), so no need to re-send it.

Nice! Progress.

On the subject of more things I could do, is there a way to reproduce
the 2 regressions? I am not familiar with IMAP, but do know how to fix
a regression. I'm not in any hurry, and do not want to promise
anything. But I'd like to take a look.

Cheers, Michiel>From 8f4c551449e90f80cdbf31fd6a0b207e9802c3e7 Mon Sep 17 00:00:00 2001
From: Michiel van den Heuvel 
Date: Thu, 17 Aug 2023 20:25:51 +0200
Subject: [PATCH] Add IncludeCmd directive to config parser

---
 src/config.c | 88 
 src/config.h |  3 ++
 src/mbsync.1 |  8 +
 3 files changed, 93 insertions(+), 6 deletions(-)

diff --git a/src/config.c b/src/config.c
index ac90c25..a7476a9 100644
--- a/src/config.c
+++ b/src/config.c
@@ -61,12 +61,21 @@ 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;
 
-	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;
 
 	int errno_bak = errno;
-	fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
+	conf_print_loc( cfile );
 	errno = errno_bak;
 	va_start( va, fmt );
 	vsys_error( fmt, va );
@@ -318,17 +327,72 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
 	return 1;
 }
 
+static void
+eval_cmd_popen( conffile_t *cfile, const char *cmd )
+{
+	if (!(cfile->eval_fp = popen( cmd, "r" ))) {
+		sys_error( "popen" );
+		cfile->err = 1;
+		return;
+	}
+	cfile->eval_line = 0;
+	cfile->eval_command = nfstrdup( cmd );
+}
+
+static void
+eval_cmd_pclose( conffile_t *cfile )
+{
+	int ret;
+
+	if ((ret = pclose( cfile->eval_fp ))) {
+		if (ret < 0) {
+			sys_error( "pclose" );
+			cfile->err = 1;
+		} else if (WIFSIGNALED( ret )) {
+			conf_error( cfile, "command \"%s\" crashed with signal %d\n",
+			cfile->eval_command, WTERMSIG( ret ) );
+		} else {
+			conf_error( cfile, "command \"%s\" exited with status %d\n",
+			cfile->eval_command, WEXITSTATUS( ret ) );
+		}
+	}
+	free( cfile->eval_command );
+	cfile->eval_fp = NULL;
+	cfile->eval_command = NULL;
+}
+
+static int
+read_cline( conffile_t *cfile )
+{
+	if (cfile->eval_fp) {
+		cfile->eval_line++;
+		if ((cfile->rest = fgets( cfile->buf, cfile->bufl, cfile->eval_fp )))
+			return 1;
+		eval_cmd_pclose( cfile );
+	}
+	cfile->line++;
+	return (cfile->rest = fgets( cfile->buf, cfile->bufl, cfile->fp )) != NULL;
+}
+
+static char *
+check_excess_tokens( conffile_t *cfile )
+{
+	char *arg = NULL;
+
+	if (cfile->rest)
+		arg = get_arg( cfile, ARG_OPTIONAL, NULL );
+	return arg;
+}
+
 int
 getcline( conffile_t *cfile )
 {
 	char *arg;
 	int comment;
 
-	if (cfile->rest && (arg = get_arg( cfile, ARG_OPTIONAL, NULL )))
+	if ((arg = check_excess_tokens( cfile )))
 		conf_error( cfile, "excess token '%s'\n", arg );
-	while (fgets( cfile->buf, cfile->bufl, cfile->fp )) {
-		cfile->line++;
-		cfile->rest = cfile->buf;
+	while (read_cline( cfile )) {
 		if (!(cfile->cmd = get_arg( cfile, ARG_OPTIONAL, &comment ))) {
 			if (comment)
 continue;
@@ -336,6 +400,16 @@ getcline( conffile_t *cfile )
 		}
 		if (!(cfile->val = get_arg( cfile, ARG_REQUIRED, NULL )))
 			continue;
+		if (!strcasecmp( cfile->cmd, "IncludeCmd" )) {
+			if (cfile->eval_fp)
+conf_error( cfile, "nested IncludeCmd\n" );
+			else if ((arg = check_excess_tokens( cfile )))
+conf_error( cfile, "excess token '%s', not executing "
+"potentially malformed command\n", arg );
+			else
+eval_cmd_popen( cfile, cfile->val );
+			continue;
+		}
 		return 1;
 	}
 	return 0;
@@ -488,6 +562,7 @@ load_config( const char *where )
 		return 1;
 	}
 	buf[sizeof(buf) - 1] = 0;
+	

Re: [PATCH] Implementing IncludeCmd (feature request #17)

2023-08-26 Thread Oswald Buddenhagen

On Sat, Aug 26, 2023 at 02:09:49PM +0200, JWM van den Heuvel wrote:
The previous mail was me sending -- instead of saving -- a draft. My 
bad.



thought so. no worries.


The perl scripts are a
work of art and make me want to punch something.


compliment accepted. :'-D


A nice future refactoring project. Looks good, but given the amount of
feedback my contributions require, not efficient if I take it up ;)


that depends on whose perspective you're assuming here.
i'm likely not going to get it done anytime soon. pushing patches on me 
prompts me into action, so it's good for the project (not so much for my 
other projects, but the trade-off isn't that bad, as i'm actually much 
more efficient at reviewing than hacking myself).

so if you think it would be a good use of *your* time, then go ahead.

> +  if (cfile->cmd && !strcasecmp( cfile->cmd, "IncludeCmd" 
> )) {

> +  if (cfile->include_fp)
> +  conf_error( cfile, "nested IncludeCmd\n" );
> +  else
> +  include_cmd_popen( cfile, cfile->val );
> +  }

wouldn't just moving the body into the conditional below work?
of course that would do the excess-argument check only after doing the
popen, but that's consistent with how it works for other options.
otoh, the side effects of instantly executing a potentially malformed
command may be somewhat more grave than when processing other options.
but handlign this properly would actually require a separate check
anyway, as then we need to actually skip the command.


The problem with doing it after the popen is that `conf_print_loc`
will then show an error as if it is in the included output while it
is in the file itself.


right.


Moving it would mean copying the excess token
detection code, which is not worth it IMHO.

i implied that you need to do that anyway to handle things properly.  
also note that you'd save the duplicated check for the command.


but looking at it again, duplication probably isn't even necessary: you 
can factor out check_excess_tokens() and make it also return a value.


the first patch is now Perfect (TM), so no need to re-send it.

regards


___
isync-devel mailing list
isync-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/isync-devel


Re: [PATCH] Implementing IncludeCmd (feature request #17)

2023-08-26 Thread JWM van den Heuvel
Hi Oswald,

The previous mail was me sending -- instead of saving -- a draft. My bad.

Anyway, here are the updated patches.

> i'm attaching it for your masochistic pleasure. there you can see that
> it's actually not even aimed at isync specifically, because c++/qt?! i
> originally wrote this some 22 years ago, when astyle was Teh Shit.
> i wouldn't expect it to actually produce invariably correct results, and
> haven't used it on isync code for ... a very long time.

Ah ok, I'll just keep formatting by hand then. The perl scripts are a
work of art and make me want to punch something. Probably not cards ;)

> depending on what you mean by "this".
>
> if you mean the "short" tangent of using a temporary: nope, not worth
> it.
>
> if you mean the "long" tangent of using xprintf: yes, but not in this
> patch series.
> for background, see the wip/better-stderr branch, which turns the
> personal aversion into an actual requirement. but notably, the config
> parser doesn't strictly _need_ that, because it runs before concurrency
> starts.

> once the infra is there, strerror(errno) (and thus perror()) can be
> handled via the %m format (stolen from syslog), so things can be unified
> pretty much. the branch shows the direction.

A nice future refactoring project. Looks good, but given the amount of
feedback my contributions require, not efficient if I take it up ;)

Thanks for the patience and guidance!

> no, i mean the saving of errno - pedantically, you need to wrap the
> first fprintf().
> otoh, if the first print fails, the second one will most likely fail as
> well, and therefore it doesn't really matter if errno gets corrupted or
> not. but as i pushed e295f483 after much deliberation, i guess we should
> keep up the good practice.

Done!

> > +static int
> > +read_cline( conffile_t *cfile )
> > +{
> > +  if (cfile->include_fp) {
> > +  cfile->include_line++;
> > +  if ((cfile->rest = fgets( cfile->buf, cfile->bufl, 
> > cfile->include_fp )))
>
> assigning cfile->rest right at the start would be less obscure. the
> pointer value is already known anyway.

That gets you an excess token error on (/after) the last line. It's not
about setting it to `buf`, but only doing it if there is actually a line
left to read.

> > +  if (cfile->cmd && !strcasecmp( cfile->cmd, "IncludeCmd" )) {
> > +  if (cfile->include_fp)
> > +  conf_error( cfile, "nested IncludeCmd\n" );
> > +  else
> > +  include_cmd_popen( cfile, cfile->val );
> > +  }
>
> wouldn't just moving the body into the conditional below work?
> of course that would do the excess-argument check only after doing the
> popen, but that's consistent with how it works for other options.
> otoh, the side effects of instantly executing a potentially malformed
> command may be somewhat more grave than when processing other options.
> but handlign this properly would actually require a separate check
> anyway, as then we need to actually skip the command.

The problem with doing it after the popen is that `conf_print_loc`
will then show an error as if it is in the included output while it
is in the file itself. Moving it would mean copying the excess token
detection code, which is not worth it IMHO.

Cheers!>From c8d835e2e3120b7ab562836ed0d21079a86e2809 Mon Sep 17 00:00:00 2001
From: Michiel van den Heuvel 
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  | 117 +-
 src/config.h  |   3 ++
 src/driver.c  |   6 +--
 src/drv_imap.c|  78 ---
 src/drv_maildir.c |  18 +++
 5 files changed, 94 insertions(+), 128 deletions(-)

diff --git a/src/config.c b/src/config.c
index 456bd47..ac90c25 100644
--- a/src/config.c
+++ b/src/config.c
@@ -61,6 +61,32 @@ expand_strdup( const char *s, const conffile_t *cfile )
 	}
 }
 
+void
+conf_error( conffile_t *cfile, const char *fmt, ... )
+{
+	va_list va;
+
+	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;
+
+	int errno_bak = errno;
+	fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
+	errno = errno_bak;
+	va_start( va, fmt );
+	vsys_error( fmt, va );
+	va_end( va );
+	cfile->err = 1;
+}
+
 char *
 get_arg( conffile_t *cfile, int required, int *comment )
 {
@@ -75,10 +101,8 @@ get_arg( conffile_t *cfile, int required, int *comment )
 	if (!c || c == '#') {
 		if (comment)
 			*comment = (c == '#');
-		if (required) {
-			error( "%s:%d: parameter missing\n", cfile->file, cfile->line );
-			cfile->err = 1;
-		}
+		if (required)
+			conf_error( cfil

Re: [PATCH] Implementing IncludeCmd (feature request #17)

2023-08-25 Thread JWM van den Heuvel
Quoting Oswald Buddenhagen (2023-08-22 15:51:20)

> On Tue, Aug 22, 2023 at 12:42:25PM +0200, Michiel van den Heuvel wrote:
> >> >I don't suppose you have some formatter config file?
> >> >
> >> bah! Real Hackers (TM) write their own formatters as shell-perl hybrids
> >> with recursive regular expressions. ON PUNCH CARDS.
> >
> >I'll trade you my magnetic needles so you can do it directly on disk
> >next time. ;)
> >
> :-)=)
>
> >Maybe just add it to the repository?
> >
> no, it feels "too off-topic".
> i'm attaching it for your masochistic pleasure. there you can see that
> it's actually not even aimed at isync specifically, because c++/qt?! i
> originally wrote this some 22 years ago, when astyle was Teh Shit.
> i wouldn't expect it to actually produce invariably correct results, and
> haven't used it on isync code for ... a very long time.
>
> >> >+  fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
> >> >+  va_start( va, fmt );
> >> >+  vfprintf( stderr, fmt, va );
> >> >
> >> totally on a tangent ...
> >> i have a bit of an aversion towards tearing apart format strings. it
> >> isn't a big deal here, because we're just printing to a stream with no
> >> concurrency, but if the target was syslog or a dynamically allocated
> >> string, things would look differently. this could be avoided by printing
> >> the "payload" to a temporary, but this would obviously have a runtime
> >> cost, esp. when using nfasprintf() to be super-safe. but as we already
> >> have xvprintf_core(), this overhead could be avoided by supporting
> >> recursive format strings: xprintf("%s:%d: %@\n", file, line, fmt, va).
> >> of course this would be total overkill here ... though once you have it,
> >> all kinds of use cases pop up. just thinking aloud ...
> >
> >Not fixed yet due to 1: the next point, and 2: do you want this?
> >
> depending on what you mean by "this".
>
> if you mean the "short" tangent of using a temporary: nope, not worth
> it.
>
> if you mean the "long" tangent of using xprintf: yes, but not in this
> patch series.
> for background, see the wip/better-stderr branch, which turns the
> personal aversion into an actual requirement. but notably, the config
> parser doesn't strictly _need_ that, because it runs before concurrency
> starts.
>
> >It would
> >introduce some duplication because conf_error and conf_sys_error print
> >to stderr and a buffer respectively. But if you see a way to avoid
> >this,
> >I'd like to hear it.
> >
> once the infra is there, strerror(errno) (and thus perror()) can be
> handled via the %m format (stolen from syslog), so things can be unified
> pretty much. the branch shows the direction.
>
> >> >+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 );
> >> >
> >> that call is theoretically unsafe; see the implementation.
> >
> >Sorry, my lack of experience with C is showing here. I looked at
> >vsys_error, but are not sure what you mean. Is it the `abort`? Do I fix
> >it with inlining `vsys_error` and a dynamically allocated buffer?
> >
> no, i mean the saving of errno - pedantically, you need to wrap the
> first fprintf().
> otoh, if the first print fails, the second one will most likely fail as
> well, and therefore it doesn't really matter if errno gets corrupted or
> not. but as i pushed e295f483 after much deliberation, i guess we should
> keep up the good practice.
>
> >Do you think that the `pclose` branch `ret < 0` error shouldn't be a
> >`conf_sys_error` but the normal one (`sys_error`)?
> >
> yeah, probably. and definitely it should match the popen one.
>

> >+static int
> >+read_cline( conffile_t *cfile )
> >+{
> >+  if (cfile->include_fp) {
> >+  cfile->include_line++;
> >+  if ((cfile->rest = fgets( cfile->buf, cfile->bufl, 
> > cfile->include_fp )))
>
> assigning cfile->rest right at the start would be less obscure. the
> pointer value is already known anyway.

That gets you an excess token error on (/after) the last line. It's not
about setting it to `buf`, but only doing it if there is actually a line
left to read.

> >@@ -325,9 +377,13 @@ getcline( conffile_t *cfile )
> >
> >+  if (cfile->cmd && !strcasecmp( cfile->cmd, "IncludeCmd" )) {
> >+  if (cfile->include_fp)
> >+  conf_error( cfile, "nested IncludeCmd\n" );
> >+  else
> >+  include_cmd_popen( cfile, cfile->val );
> >+  }
> >
> wouldn't just moving the body into the conditional below work?
> of course that would do the excess-argument check only after doing the
> popen, but that's consistent with how it works for other options.
> otoh, the side effects of instantly executing a potentially malformed
> command may be somewhat more grave than when processing other options.
> but hand

Re: [PATCH] Implementing IncludeCmd (feature request #17)

2023-08-22 Thread Oswald Buddenhagen

On Tue, Aug 22, 2023 at 12:42:25PM +0200, Michiel van den Heuvel wrote:

>I don't suppose you have some formatter config file?
>
bah! Real Hackers (TM) write their own formatters as shell-perl hybrids 
with recursive regular expressions. ON PUNCH CARDS.


I'll trade you my magnetic needles so you can do it directly on disk
next time. ;)


:-)=)


Maybe just add it to the repository?


no, it feels "too off-topic".
i'm attaching it for your masochistic pleasure. there you can see that 
it's actually not even aimed at isync specifically, because c++/qt?! i 
originally wrote this some 22 years ago, when astyle was Teh Shit.
i wouldn't expect it to actually produce invariably correct results, and 
haven't used it on isync code for ... a very long time.



>+  fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
>+  va_start( va, fmt );
>+  vfprintf( stderr, fmt, va );
>
totally on a tangent ...
i have a bit of an aversion towards tearing apart format strings. it 
isn't a big deal here, because we're just printing to a stream with no 
concurrency, but if the target was syslog or a dynamically allocated 
string, things would look differently. this could be avoided by printing 
the "payload" to a temporary, but this would obviously have a runtime 
cost, esp. when using nfasprintf() to be super-safe. but as we already 
have xvprintf_core(), this overhead could be avoided by supporting 
recursive format strings: xprintf("%s:%d: %@\n", file, line, fmt, va).
of course this would be total overkill here ... though once you have it, 
all kinds of use cases pop up. just thinking aloud ...


Not fixed yet due to 1: the next point, and 2: do you want this?


depending on what you mean by "this".

if you mean the "short" tangent of using a temporary: nope, not worth 
it.


if you mean the "long" tangent of using xprintf: yes, but not in this 
patch series.
for background, see the wip/better-stderr branch, which turns the 
personal aversion into an actual requirement. but notably, the config 
parser doesn't strictly _need_ that, because it runs before concurrency 
starts.



It would
introduce some duplication because conf_error and conf_sys_error print
to stderr and a buffer respectively. But if you see a way to avoid 
this,

I'd like to hear it.

once the infra is there, strerror(errno) (and thus perror()) can be 
handled via the %m format (stolen from syslog), so things can be unified 
pretty much. the branch shows the direction.



>+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 );
>
that call is theoretically unsafe; see the implementation.


Sorry, my lack of experience with C is showing here. I looked at
vsys_error, but are not sure what you mean. Is it the `abort`? Do I fix
it with inlining `vsys_error` and a dynamically allocated buffer?

no, i mean the saving of errno - pedantically, you need to wrap the 
first fprintf().
otoh, if the first print fails, the second one will most likely fail as 
well, and therefore it doesn't really matter if errno gets corrupted or 
not. but as i pushed e295f483 after much deliberation, i guess we should 
keep up the good practice.



Do you think that the `pclose` branch `ret < 0` error shouldn't be a
`conf_sys_error` but the normal one (`sys_error`)?


yeah, probably. and definitely it should match the popen one.


Subject: [PATCH 1/2] Refactor printing configuration errors

--- 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();


bzzt


Subject: [PATCH 2/2] Add IncludeCmd directive to config parser

--- a/src/config.c
+++ b/src/config.c
void
conf_error( conffile_t *cfile, const char *fmt, ... )
{
va_list va;

-   flushn();


ah, you committed it in the wrong patch. ^^


+static void
+include_cmd_pclose( conffile_t *cfile )
+{
...
+   free( cfile->include_command );


should null that pointer for safety/pedantry as well.


+static int
+read_cline( conffile_t *cfile )
+{
+   if (cfile->include_fp) {
+   cfile->include_line++;
+   if ((cfile->rest = fgets( cfile->buf, cfile->bufl, 
cfile->include_fp )))

assigning cfile->rest right at the start would be less obscure. the 
pointer value is already known anyway.



+   return 1;
+   include_cmd_pclose( cfile );
+   }
+   cfile->line++;
+   return (cfile->rest = fgets( cfile->buf, cfile->bufl, cfile->fp )) != 
NULL;


...


@@ -325,9 +377,13 @@ getcline( conffile_t *cfile )

+   if (cfile->cmd && !strcasecmp( cfile->cmd, "IncludeCmd" )) {
+   if (cfile->include_fp)
+   conf_error( cfile, "nested IncludeCmd\n" );
+   else
+   include_cmd_popen(

Re: [PATCH] Implementing IncludeCmd (feature request #17)

2023-08-22 Thread Michiel van den Heuvel
Hi!

Once again, a set of updated patches.

> well, you cut away the part where i kind of implied that it really 
> should be IncludeCmd. (it kind of is awkward, but i think consistency 
> trumps that.)

That might've been a bit too implicit for me. ;) Changed it to IncludeCmd.

> 
> >> on that matter, have you tested all error scenarios?
> >
> >The ones I added? Or all that were modified?
> >
> modified only to the degree that the change cannot be confidently 
> claimed to be irrelevant. so if you changed the output format or the 
> control flow, that would need testing.
> 
> >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.
> >
> yeah, that's kind of impossible without some error injection via ptrace 
> or some such. but you can fake the failure by just hacking the code. i 
> wouldn't bother with the 3rd pclose branch, but popen should be tested.

Spent some more time testing. Everything looks good to me.

> >I don't suppose you have some formatter config file?
> >
> bah! Real Hackers (TM) write their own formatters as shell-perl hybrids 
> with recursive regular expressions. ON PUNCH CARDS.
> (i can post it if you're really curious, but you need to sign a 
> liability waiver. ;-)

I'll trade you my magnetic needles so you can do it directly on disk
next time. ;)

Maybe just add it to the repository?

> >(Hopefully) All your other proposed changes were incorporated in these
> >revised patches.
> >
> mostly. but as it goes, the longer you look, the more you find. :-D

Yeah, on that note, thanks again for all the patience!

> >+  fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
> >+  va_start( va, fmt );
> >+  vfprintf( stderr, fmt, va );
> >
> totally on a tangent ...
> i have a bit of an aversion towards tearing apart format strings. it 
> isn't a big deal here, because we're just printing to a stream with no 
> concurrency, but if the target was syslog or a dynamically allocated 
> string, things would look differently. this could be avoided by printing 
> the "payload" to a temporary, but this would obviously have a runtime 
> cost, esp. when using nfasprintf() to be super-safe. but as we already 
> have xvprintf_core(), this overhead could be avoided by supporting 
> recursive format strings: xprintf("%s:%d: %@\n", file, line, fmt, va).
> of course this would be total overkill here ... though once you have it, 
> all kinds of use cases pop up. just thinking aloud ...

Not fixed yet due to 1: the next point, and 2: do you want this? It would
introduce some duplication because conf_error and conf_sys_error print
to stderr and a buffer respectively. But if you see a way to avoid this,
I'd like to hear it.

> 
> >+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 );
> >
> that call is theoretically unsafe; see the implementation.

Sorry, my lack of experience with C is showing here. I looked at
vsys_error, but are not sure what you mean. Is it the `abort`? Do I fix
it with inlining `vsys_error` and a dynamically allocated buffer?

> signal %d",... WTERMSIG().
> pedantically, that quoting is insufficient because it doesn't do 
> escaping, but there's no need to overdo it here.

That's exactly the reason my inner pedant thought it was best to use
the previous version. But, changed it.

> this isn't quite right; it would miss excessive arguments.
> you can probably use `return getcline()` (and rely on the compiler 
> eliding the tail recursion).

Yes. Because the error message needs to show the correct line
(`include_fp` must still be null when printing the error message)
I reorganized the code a bit.

> come to think of it, you were probably right to make a separate section 
> for it. but it may be better to put it at the end, after the global one 
> - it's kind of more like the global options. otoh, it's kind of a 
> syntactic/structural part of the config, so having it at the top also 
> makes sense. hmm.

Well, I now put your suggestion verbatim at the bottom. Choices...

Do you think that the `pclose` branch `ret < 0` error shouldn't be a
`conf_sys_error` but the normal one (`sys_error`)?

Cheers!>From 5c47b627095a5b69c7096f667dfd441f86e163d6 Mon Sep 17 00:00:00 2001
From: Michiel van den Heuvel 
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  | 116 +-
 src/config.h  |   3 ++
 src/driver.c  |   6 +--
 src/drv_imap.c|  78 ---
 src/drv_maildir.c |  18 +++
 5 files changed, 93 insertions(+), 128 deletions(-)

diff --git a/src/config.c b/s

Re: [PATCH] Implementing IncludeCmd (feature request #17)

2023-08-18 Thread Oswald Buddenhagen

On Thu, Aug 17, 2023 at 08:27:51PM +0200, Michiel van den Heuvel wrote:

eval => included, i guess.


Changed it, but are you sure? Now that the command is called `Eval`?

well, you cut away the part where i kind of implied that it really 
should be IncludeCmd. (it kind of is awkward, but i think consistency 
trumps that.)



on that matter, have you tested all error scenarios?


The ones I added? Or all that were modified?

modified only to the degree that the change cannot be confidently 
claimed to be irrelevant. so if you changed the output format or the 
control flow, that would need testing.



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.

yeah, that's kind of impossible without some error injection via ptrace 
or some such. but you can fake the failure by just hacking the code. i 
wouldn't bother with the 3rd pclose branch, but popen should be tested.



> +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?


i'm not sure ...
technically, the failed include could have structural consequences, as 
the command may be intending to start a new section, which is continued 
inline. otoh, it would be really stupid to write a config that does 
that.
and we live with followup errors anyway, so accepting some more wouldn't 
be a big deal. so i guess no change needed.



I don't suppose you have some formatter config file?

bah! Real Hackers (TM) write their own formatters as shell-perl hybrids 
with recursive regular expressions. ON PUNCH CARDS.
(i can post it if you're really curious, but you need to sign a 
liability waiver. ;-)



(Hopefully) All your other proposed changes were incorporated in these
revised patches.


mostly. but as it goes, the longer you look, the more you find. :-D


Subject: [PATCH 1/2] Refactor printing configuration errors

--- a/src/config.c
+++ b/src/config.c

+void
+conf_error( conffile_t *cfile, const char *fmt, ... )
+{
+   va_list va;
+
+   flushn();


that's unnecessary, for the same reason that +cmd is.


+   fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
+   va_start( va, fmt );
+   vfprintf( stderr, fmt, va );


totally on a tangent ...
i have a bit of an aversion towards tearing apart format strings. it 
isn't a big deal here, because we're just printing to a stream with no 
concurrency, but if the target was syslog or a dynamically allocated 
string, things would look differently. this could be avoided by printing 
the "payload" to a temporary, but this would obviously have a runtime 
cost, esp. when using nfasprintf() to be super-safe. but as we already 
have xvprintf_core(), this overhead could be avoided by supporting 
recursive format strings: xprintf("%s:%d: %@\n", file, line, fmt, va).
of course this would be total overkill here ... though once you have it, 
all kinds of use cases pop up. just thinking aloud ...



+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 );


that call is theoretically unsafe; see the implementation.


@@ -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" );
}


the braces are now excessive.
this repeats in a few places, in some of which it will probably have a 
cascading effect due to the brace symmetry requirement.



--- 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 *, ... );
+
i'd probably put this above expand_strdup(), simply because it would 
look better due to the macros not standing out that much.


oh, and you pointed out to me that i'm being inconsistent about 
including parameter names in the print/error function prototypes. that 
omission is a mistake and should not be copied.



Subject: [PATCH 2/2] Add Eval directive to config parser

--- a/src/config.c
+++ b/src/config.c
+static void
+eval_popen( conffile_t *cfile, const char *cmd )
+{
+   if (!(cfile->eval_fp = popen( cmd, "r" ))) {
+   s

Re: [PATCH] Implementing IncludeCmd (feature request #17)

2023-08-17 Thread Michiel van den Heuvel
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 
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

Re: [PATCH] Implementing IncludeCmd (feature request #17)

2023-08-17 Thread Oswald Buddenhagen

hi!

On Thu, Aug 17, 2023 at 10:59:48AM +0200, Michiel van den Heuvel wrote:

I named the command `Eval` instead of `IncludeCmd` because it seemed
clearer to me, but you're welcome to change that.

my thinking was that IncludeCmd would nicely complement a hypothetical 
Include command, following the structure of the pre-existing *Cmd 
commands.

the code needs no renaming, eval is nice and short.


Patch 1 was necessary to print error line numbers correctly after adding
`Eval` without modifying every line where errors are printed.


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.


On the other hand, it is a formatting function...


nah, the name doesn't say so.


Patch 2 is where the actual work happens. I indeed tried to follow
`cred_from_cmd`.



Nesting `Eval` statements is disallowed. Implementing
this would certainly be doable, but I don't really see the point.  


agreed.


I was working on an extra patch, which would extend the parser to
allow multiline values for slightly longer commands. [...]

 - Do you see merit in this feature? I don't currently need it, but
   like configuration file formats which offer possibilities to split
   lines if they get too long.

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. ;)



 - If so, what would you think is the better implementation? Just make
   the buffer really large (what is large?) and error if a script is
   longer? The current line limit is 1024. [...]

i think the static 1k buffer is just fine - that amounts to a script 
with up to ~20 lines, and it seems a tad insane to inline more than 
that.



 - Do you agree with the syntax? This still requires backslashes even
   when quoted, which I like due to the missing closing quote error
   which would otherwise be hard to correctly detect.

i guess. it also has the advantage of somewhat signaling the fact of the 
extra layer of quoting.



 - What should the behaviour be when not quoted?
A literal newline (current implementation)?

that seems convenient, but it violates expectations about how line 
continuations usually behave, regardless of whether quoted or not.



Read it as whitespace?

that would be more conventional. it would of course also mean that users 
need to include `&&` (or `;`) to chain the commands, which many would 
probably get wrong on first try.


the alternative would be more like a here-doc, and would warrant a 
corresponding syntax. seems like mild overkill, though.


so just call a script already! ;)


Subject: [PATCH 2/2] Add Eval directive to config parser

--- a/src/config.c
+++ b/src/config.c

+void


static


+conf_print_loc( const conffile_t *cfile )
+{
+   if (cfile->eval_fp != NULL) {


i treat pointers a booleans in conditionals.
also, excess braces.


+   fprintf( stderr, "%s:%d:eval %d: ", cfile->file, cfile->line, 
cfile->eval_line );


eval => included, i guess.
also, that space afterwards should be probably a colon, otherwise it's 
kinda confusing and weird-looking.


on that matter, have you tested all error scenarios?


+void

should return an int (zero is error), which should lead to early 
termination.

also, static.


+eval_popen( conffile_t *cfile, const char *cmd )
+{
+   if (*cmd == '+') {
+   flushn();
+   cmd++;
+   }

that makes no sense here - unlike the credential commands, this is 
executed at program startup where no progress info has been printed yet.

don't forget to prune it from the docu as well.


+   if (!(cfile->eval_fp = popen( cmd, "r" ))) {
+   sys_error( "%s failed", cmd );

i think it makes no sense to print the command here, as failure here 
would be a fork() failure or something like that. so just "popen" should 
do.

(though i wonder how things would look like in an msys build.)


+void


as above.


+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" );
+   else if (WIFSIGNALED( ret ))
+   conf_error( cfile, "command crashed\n" );
+   else
+   conf_error( cfile, "command exited with status %d\n", 
WEXITSTATUS( ret ) );


... 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.


here-doc syntax would make printing the command superfluous, because it 
would be used verbatim anyway ... until someone actually implements 
expandos (like %{Host}).



+char *


i think that should be just an int.
also, static.


+rea

[PATCH] Implementing IncludeCmd (feature request #17)

2023-08-17 Thread Michiel van den Heuvel
Hi!

It took some time, but here is an implementation of feature request
#17. I named the command `Eval` instead of `IncludeCmd` because it seemed
clearer to me, but you're welcome to change that.

Patch 1 was necessary to print error line numbers correctly after adding
`Eval` without modifying every line where errors are printed. It might
be worthwhile to set `cfile->error` here too, and I'll gladly amend the
patch if you agree. On the other hand, it is a formatting function...

Patch 2 is where the actual work happens. I indeed tried to follow
`cred_from_cmd`. Nesting `Eval` statements is disallowed. Implementing
this would certainly be doable, but I don't really see the point.  

I was working on an extra patch, which would extend the parser to
allow multiline values for slightly longer commands. For example:

Eval "\
echo User $USER \
echo Path $XDG_DATA_HOME/notmuch/default \
"

Due to the way the `cfile->buf` buffer is aliased by `cfile->cmd` etc
this proved to be a little bit more difficult than I initially thought. I
have a few questions:

  - Do you see merit in this feature? I don't currently need it, but
like configuration file formats which offer possibilities to split
lines if they get too long.
  - If so, what would you think is the better implementation? Just make
the buffer really large (what is large?) and error if a script is
longer? The current line limit is 1024. The other option I see is
dynamically reallocating the buffer (I have already played with this,
but sadly that implementation was not aware of comments) and updating
the pointers in `cfile` as needed. Maybe throw in a `restrict`.
  - Do you agree with the syntax? This still requires backslashes even
when quoted, which I like due to the missing closing quote error
which would otherwise be hard to correctly detect.
  - What should the behaviour be when not quoted? A literal newline
(current implementation)? Read it as whitespace?

Anyway, valgrind was an issue on my end and is fixed. So it has been
tested with it, hopefully no leaks this time ;)

Once again, feedback is very welcome!

Kind regards,  
Michiel van den Heuvel>From 1ef12e2b3f205af4324565183b700e27ecc88830 Mon Sep 17 00:00:00 2001
From: Michiel van den Heuvel 
Date: Wed, 16 Aug 2023 20:36:48 +0200
Subject: [PATCH 2/2] Add Eval directive to config parser

---
 src/config.c | 81 +---
 src/config.h |  2 ++
 src/mbsync.1 | 10 +++
 3 files changed, 89 insertions(+), 4 deletions(-)

diff --git a/src/config.c b/src/config.c
index 3aa3f7c..6e08dd9 100644
--- a/src/config.c
+++ b/src/config.c
@@ -61,13 +61,23 @@ expand_strdup( const char *s, const conffile_t *cfile )
 	}
 }
 
+void
+conf_print_loc( const conffile_t *cfile )
+{
+	if (cfile->eval_fp != NULL) {
+		fprintf( stderr, "%s:%d:eval %d: ", cfile->file, cfile->line, cfile->eval_line );
+	} else {
+		fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
+	}
+}
+
 void
 conf_error( const 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 );
@@ -78,7 +88,7 @@ conf_sys_error( const conffile_t *cfile, const char* msg, ... )
 {
 	va_list va;
 
-	fprintf( stderr, "%s:%d: ", cfile->file, cfile->line );
+	conf_print_loc( cfile );
 	va_start( va, msg );
 	vsys_error( msg, va );
 	va_end( va );
@@ -326,6 +336,58 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
 	return 1;
 }
 
+void
+eval_popen( conffile_t *cfile, const char *cmd )
+{
+	if (*cmd == '+') {
+		flushn();
+		cmd++;
+	}
+	if (!(cfile->eval_fp = popen( cmd, "r" ))) {
+		sys_error( "%s failed", cmd );
+		return;
+	}
+	cfile->eval_line = 0;
+}
+
+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" );
+		else if (WIFSIGNALED( ret ))
+			conf_error( cfile, "command crashed\n" );
+		else
+			conf_error( cfile, "command exited with status %d\n", WEXITSTATUS( ret ) );
+	}
+}
+
+char *
+read_cline( conffile_t *cfile )
+{
+	char *ret;
+
+	ret = cfile->buf;
+	if (cfile->eval_fp) {
+		ret = fgets( cfile->buf, cfile->bufl, cfile->eval_fp );
+		cfile->eval_line++;
+		if (!ret) {
+			eval_pclose( cfile );
+			ret = cfile->buf;
+		}
+	}
+	if (!cfile->eval_fp && ret) {
+		ret = fgets( cfile->buf, cfile->bufl, cfile->fp );
+		cfile->line++;
+	}
+	return ret;
+}
+
 int
 getcline( conffile_t *cfile )
 {
@@ -336,14 +398,24 @@ getcline( conffile_t *cfile )
 		conf_error( cfile, "excess token '%s'\n", arg );
 		cfile->err = 1;
 	}
-	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)
 c