Saturday 29 September 2012

Running a program and interacting with it.

Here's an example of a function, ccl_popen3(), that opens a program with the standard streams that you want to write to/read from open. It's from a while ago, I'll brush it up later (in theory).

Either way, ccl_popen3() is the money function, and main() demonstrates usage. Compiles on my machine with gcc popen3.c -o popen3 -std=gnu99 -Wall -Wextra -Werror.
#include <stdlib.h>
#include <unistd.h>

#define READ_END  0
#define WRITE_END 1
#define NENDS     2

#define OPEN_STDIN  (1 << STDIN_FILENO)
#define OPEN_STDOUT (1 << STDOUT_FILENO)
#define OPEN_STDERR (1 << STDERR_FILENO)
#define OPEN_ALL    (OPEN_STDIN | OPEN_STDOUT | OPEN_STDERR)
#define MAX_STREAMS 3



__attribute__((noreturn))
static void popen3_child(const char *exe,
                         char *const argv[],
                         int shell,
                         int streams,
                         int pipes[MAX_STREAMS][NENDS])
{
    for (int i = 0 ; i < MAX_STREAMS ; i++)
        if (streams & (1 << i)) {
            close(pipes[i][i == STDIN_FILENO ?
                           WRITE_END : READ_END]);
            if (dup2(pipes[i][i == STDIN_FILENO
                              ? READ_END : WRITE_END],
                     i) == -1)
                exit(127);
        }

    if (shell)
        execvp(exe, argv);
    else
        execv(exe, argv);

    /* Any return from execv()/execvp() is an error. */
    exit(127);
}

static void popen3_parent(int streams,
                          int fds[],
                          int pipes[][NENDS])
{
    for (int i = 0 ; i < MAX_STREAMS ; i++)
        if (streams & (1 << i)) {
            close(pipes[i][i == STDIN_FILENO ?
                           READ_END : WRITE_END]);
            fds[i] = pipes[i][i == STDIN_FILENO ?
                              WRITE_END : READ_END];
        }
}



/*
 * TODO: (i) different functions for child and parent, (ii)
 * close original pipe fd's.
 */
pid_t ccl_popen3(const char *exe,
                 char *const argv[],
                 int shell,
                 int streams,
                 int fds[])
{
    int pipes[MAX_STREAMS][NENDS] = {{-1, -1},
                                     {-1, -1},
                                     {-1, -1}};

    for (int i = 0 ; i < MAX_STREAMS ; i++)
        if ((streams & (1 << i)) && (pipe(pipes[i]) == -1))
            goto err;

    pid_t pid = fork();
    switch (pid) {
    case -1:  /* error */
        goto err;
    case 0:   /* child */
        popen3_child(exe, argv, shell, streams, pipes);
        break;
    default:  /* parent */
        popen3_parent(streams, fds, pipes);
        return pid;
    }

 err:
    for (int p = 0 ; p < MAX_STREAMS ; p++)
        for (int e = 0 ; e < NENDS ; e++)
            if (pipes[p][e] != -1)
                close(pipes[p][e]);

    return -1;
}



#include <poll.h>
#include <stdio.h>

void ccl_poll_input(int fds[3])
{
    struct pollfd pollfds[2] = {
        { .fd = fds[STDOUT_FILENO], .events = POLLIN },
        { .fd = fds[STDERR_FILENO], .events = POLLIN }
    };

    int running = 1;
    while (running) {
        int res = poll(pollfds, 2, -1);

        if (res == -1) {
            perror("poll()");
            exit(EXIT_FAILURE);
        }

        struct pollfd *pfd = pollfds;
        for (int i = 0 ; i < 2 ; i++, pfd++) {
            if (pfd->revents & (POLLERR | POLLNVAL)) {
                fprintf(stderr, "poll() error\n");
                exit(EXIT_FAILURE);
            }

            if (pfd->revents & (POLLIN | POLLHUP)) {
                char buf[1024];
                ssize_t nread = read(pfd->fd,
                                     buf, sizeof(buf) - 1);

                if (nread == -1) {
                    perror("read()");
                    exit(EXIT_FAILURE);
                }

                if (nread == 0) {
                    /* EOF */
                    running = 0;
                    break;
                }

                buf[nread] = '\0';
                printf(" Read from %d: \"%s\"\n",
                       pfd->fd, buf);
            }
        }
    }
}



#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <poll.h>

int main(void)
{
    int grep_fds[3];

    const char *exe = "/bin/grep";
    char *const argv[] =
      {"grep", "-h", "XX", "jkfdsl", "-", NULL};
    
    pid_t grep_pid =
      ccl_popen3(exe, argv, 0, OPEN_ALL, grep_fds);

    if (grep_pid == -1) {
        perror("ccl_popen3()");
        exit(EXIT_FAILURE);
    }

    /* Write some input to grep's standard input, and close
     * it. */
    const char *input = "abcd\ngg9XXp\nDqdXXpird\n";
    if (write(grep_fds[STDIN_FILENO], input, strlen(input))
        != (ssize_t)strlen(input)) {
        perror("write()");
        exit(EXIT_FAILURE);
    }
    close(grep_fds[STDIN_FILENO]);

    printf("out: %d, err: %d\n", grep_fds[STDOUT_FILENO],
           grep_fds[STDERR_FILENO]);

    struct pollfd pollfds[2] = {
        { .fd = grep_fds[STDOUT_FILENO], .events = POLLIN },
        { .fd = grep_fds[STDERR_FILENO], .events = POLLIN }
    };

    int running = 1;
    while (running) {
        int res = poll(pollfds, 2, -1);

        if (res == -1) {
            perror("poll()");
            exit(EXIT_FAILURE);
        }

        struct pollfd *pfd = pollfds;
        for (int i = 0 ; i < 2 ; i++, pfd++) {
            if (pfd->revents & (POLLERR | POLLNVAL)) {
                fprintf(stderr, "poll() error\n");
                exit(EXIT_FAILURE);
            }

            if (pfd->revents & (POLLIN | POLLHUP)) {
                char buf[1024];
                ssize_t nread = read(pfd->fd,
                                     buf, sizeof(buf) - 1);

                if (nread == -1) {
                    perror("read()");
                    exit(EXIT_FAILURE);
                }

                if (nread == 0) {
                    /* EOF */
                    running = 0;
                    break;
                }

                buf[nread] = '\0';
                printf(" Read from %d: \"%s\"\n",
                       pfd->fd, buf);
            }
        }
    }

    int grep_status;
    if (waitpid(grep_pid, &grep_status, 0) == -1) {
        perror("waitpid()");
        exit(EXIT_FAILURE);
    }

    if (WIFEXITED(grep_status))
        printf("grep exited with status %d\n",
               WEXITSTATUS(grep_status));
    else
        // TODO: This better.
        printf("grep terminated abnormally\n");
    return EXIT_SUCCESS;
}

No comments:

Post a Comment