views:

128

answers:

2

I'm trying to write an app that can login to SSH with a password, by using pseudo terminals. But if I write() to the master device then the data somehow does not appear in the slave device. Here's a simple test case:

#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#ifdef __linux__
    #include <pty.h>
#else
    #include <util.h>
#endif

int
main() {
    int master;
    pid_t pid = forkpty(&master, NULL, NULL, NULL);
    if (pid == 0) {
        int ch;
        read(0, &ch, 1);
        _exit(1);
    } else {
        printf("Press Enter to send a byte.\n");
        getchar();
        write(master, "1", 1);
        printf("Done. Waiting for process to exit...\n");
        waitpid(pid, NULL, 0);
        return 0;
    }
}

The app will first output "Press Enter to send a byte". After pressing Enter, I expect the child process's read() to return. But the read() there seems to block indefinitely even though the master's write() succeeds, so the master waits forever on the waitpid(). What's going on?

+1  A: 

The example code at this blog post should probably be of help. The author has an update to his original problem (very similar to yours) with a usable spawn (char *argv[]); function given.

hlovdal
+1  A: 

The problem is that you didn't modify the line discipline of the PTY. The default line discipline is line oriented, so no input will be sent to the slave process until a newline character is read. (You can see this by sending a "\n" to the slave, instead of just "1".) You can run the PTY in RAW mode by, in the child process, calling tcgetattr, cfmakeraw and tcsetattr, like follows:

    if (pid == 0) {
        int ch;
        struct termios t;
        tcgetattr(0, &t);
        cfmakeraw(&t);
        tcsetattr(0, TCSANOW, &t);
        read(0, &ch, 1);
        _exit(1);
    } else {

This seems to work for me.

Aidan Cully