This is a frontend to the tool dc
; the idea is to type an infix expression (2 + 3), map it to the corresponding postfix notation (2 3 +) and send it to dc
. It's what bc
does.
I'm doing this with pipes, but the frontend hangs waiting for output.
This is the code; I will continue commenting it below. The "h" command in my dc
is unimplemented, hence is what I'm looking for as an end of output from dc
.
TL;DR: the process hangs because stdout
in dc
is not flushed or that's what I think to have found. How can I read it regardless or force a flush after every write?
What I've found is commented below.
#define DC_EOF_RCV "dc: 'h' (0150) unimplemented"
#define DC_EOF_SND "h\n"
static FILE *sndfp, *rcvfp;
int dcinvoke() {
int pfdout[2], pfdin[2];
pid_t pid;
pipe(pfdout);
pipe(pfdin);
switch (pid = fork()) {
case -1: exit(1);
case 0:
dup2(pfdout[0], STDIN_FILENO);
dup2(pfdin[1], STDOUT_FILENO);
close(pfdout[0]);
close(pfdout[1]);
close(pfdin[0]);
close(pfdin[1]);
execlp("dc", "dc", "-", NULL);
}
close(pfdout[0]);
close(pfdin[1]);
sndfp = fdopen(pfdout[1], "w");
rcvfp = fdopen(pfdin[0], "r");
return 1;
}
void dcsnd(const char *s) {
fputs(s, sndfp);
fflush(sndfp);
}
void dcrcv(char *buf, size_t max) {
fgets(buf, max, rcvfp); // <<<<< HANGS HERE
}
int turnaround() {
dcsnd(DC_EOF_SND); fflush(sndfp);
}
int rcvall() {
char buf[256];
turnaround();
for (;;) {
dcrcv(buf, sizeof(buf));
if (! strcmp(buf, DC_EOF_RCV)) {
break;
}
printf("%s", buf);
}
return 1;
}
int prompt(const char *msg, char *res, size_t resmax, int *eofp) {
char *p;
printf("\n%s ", msg);
if (!fgets(res, resmax, stdin)) {
*eofp = 1;
} else {
if (p = strrchr(res, '\n')) {
*p = 0;
}
*eofp = 0;
}
return 1;
}
int main() {
char buf[128], line[128];
int eof;
dcinvoke();
dcsnd("2 3 +\n");
dcsnd(DC_EOF_SND);
rcvall();
for (;;) {
prompt("Expression", buf, sizeof(buf), &eof);
if (eof) {
break;
}
snprintf(line, sizeof(line), "%s\n", buf);
dcsnd(line);
rcvall();
}
dcsnd("q\n");
return 0;
}
I have removed the error checks for simplicity of this question.
The frontend hangs at the line:
fgets(buf, max, rcvfp);
As I really had no idea how to find the problem, I wrote my own dc
which does nothing but responds correctly to an "h" command and outputs everything it gets to a file (so I can debug it; I am not aware of any other way). I can add it here if it's useful.
I've found that a call to fflush(stdout)
in (my) dc
resumes the frontend, as a call to fgets
finally returns.
I cannot change dc
itself. How can it not hang on fgets
?
Some ideas I had:
Use another thread to flush rcvfp
(stdout
of dc
)
Write it using pseudoterminal
I'm looking for suggestions or a simple way to avoid both of them.
I tried to:
dc sends error messages to stderr, not stdout, so you'll never see them -- they go straight to the terminal (or whatever stderr is connected to in your environment). To be able to read them in your program, you also need to redirect stderr. Add
dup2(pfdin[1], STDERR_FILENO);
in the child fork code.
To make your code actually work, I had to also add a newline to the end of the DC_EOF_RCV string (to match what dc sends), and remove the extra dcsnd(DC_EOF_SND);
call from main, since you never do a receive that matches it anywhere. Even with this, you can still get race conditions within dc between writing to stdout and stderr, which can cause the output lines to get mixed, resulting in a response that does not match your DC_EOF_RCV string. You can probably avoid those by adding a small sleep to your turnaround
function.
An alternate option is using poll in your main loop to read from stdin and dc simultaneously. With this, you don't need the weird "send h command to trigger an error" mechanism to figure out when dc is done -- you just loop. Your main loop ends up looking like:
struct pollfd polling[2] = { { STDIN_FILENO, POLLIN, 0 }, { fileno(rcvfp), POLLIN, 0 } };
printf("Expression ");
for(;;) {
if (poll(polling, 2, -1) < 0) {
perror("poll");
exit(1); }
if (polling[0].revents) {
/* stdin is ready */
if (!fgets(buf, sizeof(buf), stdin))
break;
dcsnd(buf); }
if (polling[1].revents) {
/* dc has something for us */
printf("\r \r"); /* erase the previsouly printed prompt */
dcrcv(buf, sizeof(buf));
printf("%s\n", buf); }
printf("Expression "); /* new prompt */
}
There's more detail you can do here with managing error conditions (the revents
might be POLLERR
or POLLHUP
for an error or hangup rather than POLLIN
for normal input -- all of these are non-zero values, so testing just for nonzero is reasonable).