3
votes

I was trying to mock up a simple GUI for a C program, by forking the C program and exec-ing wish in the child, then piping a bunch of tcl/tk commands into it from the parent. After the form is created, I would let the C program just keep reading the output of the tcl program and respond to it.

I have it mostly working, but in this example I keep getting a message from tcl:

invalid command name ""

It's not breaking anything but I don't really understand what's causing it.

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define DIE do { perror(__FUNCTION__); exit(EXIT_FAILURE); } while (0)
#define LEN(a) (sizeof(a) / sizeof(*a))

int child(int p2c[2], int c2p[2]) {
    if (close(p2c[1])) DIE;
    if (close(c2p[0])) DIE;

    if (dup2(p2c[0], fileno(stdin)) < 0) DIE;
    if (dup2(c2p[1], fileno(stdout)) < 0) DIE;

    char * cmds[] = { "wish", NULL };
    execvp(cmds[0], cmds);
    DIE;
}

int parent(int p2c[2], int c2p[2]) {
    if (close(p2c[0])) DIE;
    if (close(c2p[1])) DIE;

    char init[] = "button .b -text {Print} -command {puts hi}\n"
                  "button .x -text {Exit} -command {exit}\n"
                  "grid .b -column 0 -row 0\n"
                  "grid .x -column 0 -row 1\n";
    if (write(p2c[1], init, LEN(init)) < LEN(init)) DIE;
    if (close(p2c[1])) DIE;

    char buf[1<<10];
    ssize_t s;

    while ((s = read(c2p[0], buf, LEN(buf))) > 0) {
        buf[s-1] = '\0';

        printf("i read '%s'\n", buf);
    }

    return 0;
}

int main(int argc, char ** argv) {
    int p2c[2]; // parent to child pipe
    int c2p[2];

    if (pipe(p2c)) DIE;
    if (pipe(c2p)) DIE;

    switch (fork()) {
        case -1: DIE;              break;
        case  0: child(p2c, c2p);  break;
        default: parent(p2c, c2p); break;
    }

    return EXIT_SUCCESS;
}

Any idea why it's claiming that the empty string is an invalid command?

1

1 Answers

3
votes

The problem is that your expression for determining the length of a string is wrong. strlen(init) will produce a value that is one less than LEN(init) returns, because the latter includes the terminating NUL character in the count. This means that you write that NUL out on the pipe (where it doesn't belong) and Tcl interprets it as a strange (but legal!) command name. There's no such command, of course (unless you do proc \u0000 {} {puts BOOM}) but it still checks to see if anything that it knows about in its script autoloading mechanism can supply that command. (Adding that little procedure into the script that you send over — remembering to double the \ — gets this message written out instead:

i read 'BOOM
'

QED.

If you change the write to actually send the correct number of bytes, everything works as you expect.