1

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:

  • Use read() to fileno(rcvfp)

  • Read this and this similar posts

  • Use setlinebuf() (man)

S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
bicup
  • 305
  • 1
  • 11
  • Probable reason: Because *dc* does not have a tty, its stdout is in fully buffered mode. You need to either give it a pty to make output line buffered, or make it exit after every operation to force flushing of its stdout. – hyde Aug 30 '19 at 20:05
  • Aside: as the rules about output flushing vary, after `printf("\n%s ", msg);`, consider `fflush(stdout);` to insure output occurs before `fgets()`. – chux - Reinstate Monica Aug 30 '19 at 20:23
  • 1
    You should have error code (even if it is just `exit(EXIT_FAILURE);`) after the `execlp()` — it might fail and then it is important to exit rather than trying to continue. You don't need to test the return value from `execlp()`; it won't return if it succeeds; if it returns, it failed. You might also write an error to `stderr` before exiting. – Jonathan Leffler Aug 31 '19 at 00:30
  • Modern versions of `bc` often do not use `dc`. POSIX does not standardize the use of `dc` (`dc` isn't part of POSIX, but `bc` is; the rationale mentions `dc` and its use by some implementations of `bc`). – Jonathan Leffler Aug 31 '19 at 00:39
  • @JonathanLeffler every single call is wrapped around error checking macros, even that exec, but as I say in the question, `I have removed the error checks for simplicity of this question.`. There's no need to use `dc` other than as an exercise on using pipes... I thought it was going to be easier and found it was more interesting than at first thought – bicup Aug 31 '19 at 07:27
  • I don't regard `exit(1);` after `exec*()` as error checking — it's just necessary code. I didn't discuss any other missing error checks because of your comment. I guess it comes down to attitude — you can sort of make a case for "it's an error check", but I wouldn't readily agree with you. – Jonathan Leffler Aug 31 '19 at 07:52

2 Answers2

0

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

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • This is something to keep in mind, but it doesn't solve the problem here. I tried nevertheless and it still gets stuck waiting for data from the pipe – bicup Aug 31 '19 at 07:28
  • @bicup: but not until it reads and outputs the two error messages from dc. Note that your comparison againt `DC_EOF_RCV` in `rcval` will never match due to the newline, so that loop will never exit. If you fix that, get to the main loop calling prompt. – Chris Dodd Aug 31 '19 at 18:22
  • Using `strstr(buf, DC_EOF_RCV)` does not help – bicup Aug 31 '19 at 18:49
  • By your edit, I removed that line from `main` and used `strstr` with `DC_EOF_RCV "'h' (0150) unimplemented"`, plus a `sleep(1)`, but for me it does not work. Does it not deadlock for you? – bicup Aug 31 '19 at 19:28
  • No deadlocks with a `strstr` like you describe. Try running with `strace -f` to see what dc is doing internally.... – Chris Dodd Aug 31 '19 at 19:41
  • `dc` is blocked reading from stdin, the other blocked reading from the pipe. I don't understand how it can work for you, but your `select`/`poll` is an interesting choice. If you add it to this answer, I'll consider it a viable solution – bicup Aug 31 '19 at 19:51
0

With the dc command provided by macOS 10.14.6 Mojave, sending 2 3 + to the program works fine; it just doesn't say anything because you've not asked it to.

$ dc
2 3 +
p
5
q
$

The p (print) command triggers the response 5. The q command quits.

Sending h gets a mixed response:

$ dc
h
dc: 'h' (0150) unimplemented
q
$

The response is mixed because the dc: (and space) goes to standard error and the message to standard output:

$ dc >/dev/null
h
dc:
q
$

$ dc 2>/dev/null
h
'h' (0150) unimplemented
q
$

Consequently, I think your primary problem is that you are waiting for a response that dc is not going to provide. If you want to ensure that you get a printed value, then you need to add a p command. That can be on the same line or on a different line as the 2 3 + expression.

$ dc
2 3 + p
5
2
3
+
p
5
q
$
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • The problem is that this should just be a frontend; until the user types `p`, it should not assume there is an implicit `p` in the command - that's my take at least. In fact `dc` can use a stack and push/pop expression results... so is it `p` or `n` or another command? – bicup Aug 31 '19 at 18:50
  • Then you need to design a “read with timeout” mechanism such that if there’s going to be an answer, you get it, and if there isn’t, you don’t. You can’t afford to deadlock with `dc` waiting for input from your program and your program waiting for output from `dc`, but that’s what’s happening. The timeout should probably be short — tens to hundreds of milliseconds. Or you could add the `p` to ensure there's a response; it is a non-destructive operation. What should happen if the user includes several `p` operations in the typed expression? `2 3 + p 4 5 * p - p` generates `5 20 -15`on 3 lines. – Jonathan Leffler Aug 31 '19 at 18:54
  • 1
    Alternately use `select` or `poll` to read from either dc or the user "simultaneously" and handle whichever one you get. – Chris Dodd Aug 31 '19 at 18:59
  • With `bc`, of course, the `bc` program is generating the program (expressions) run by `dc` and controls everything that's sent and knows what to expect back from the expressions it sends. – Jonathan Leffler Aug 31 '19 at 19:04