tags:

views:

120

answers:

2

Hello colleagues!

I have prepared a program which emulates shell (cmd) interface using pipes. There are two versions of the program: 1. Using one pipe (using a pipe from parent to child communication) 2. Using double pipe (using two pipes from parent to child and from child to parent to communicate).

So, the first program provides desired interface and works how I want, but I cannot reach the same result (interface) in the second program (using dup2() and similar).

So, I relay on your help and put the both codes below.

B.S.: You may compile and try both programs with the same way using these commands:

$ gcc prog1.c -o prog1

Next let's run:

$ ./prog1

Next let's run new terminal and try to write some data to input.txt:

$ echo pwd > input.txt

And then watch the result in the first terminal.

(This working fine for the first program but I need to get this working wit the same interface in the second program)

CODE OF THE FIRST PROGRAM (WORKING FINE):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

void do_child(int data_pipe[]) {
    int c;
    int rc;
    close(data_pipe[1]);

    dup2(data_pipe[0], 0); /* This string provides the desired interface of the program */

    char* cmd[] = { "bash", (char *)0 };
    execvp("bash", cmd);

    while ((rc = read(data_pipe[0], &c, 1)) > 0) 
    {
        putchar(c);
    }
    exit(0);
}

void do_parent(int data_pipe[])
{
    int c;
    int rc;
    FILE *in;

    close(data_pipe[0]);

    while (1)
    {
        in = fopen("input.txt", "r");
        while ((c = fgetc(in)) > 0) 
        {
            rc = write(data_pipe[1], &c, 1);
            if (rc == -1) 
            {
                perror("Parent: write");
                close(data_pipe[1]);
                exit(1);
            }
        }
        fclose(in);
    }
    close(data_pipe[1]);
    exit(0);
}

int main(int argc, char* argv[])
{
    int data_pipe[2];
    int pid;
    int rc;

    umask(0);
    mknod("input.txt", S_IFIFO|0666, 0);

    rc = pipe(data_pipe);
    if (rc == -1) 
    {
        perror("pipe");
        exit(1);
    }
    pid = fork();
    switch (pid) 
    {
    case -1:
        perror("fork");
        exit(1);
    case 0:
        do_child(data_pipe);
    default:
        do_parent(data_pipe);
    }
    return 0;
}

CODE OF THE SECOND PROGRAM (NEED TO BE CORRECTED A LITTLE BIT):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

/* Original version got from http://www.iakovlev.org */

int parent_to_child[2];
int child_to_parent[2];

void do_parent()
{
    int c;
    char ch;
    int rc;
    FILE *in;

    close(child_to_parent[1]); /* we don't need to write to this pipe.  */
    close(parent_to_child[0]); /* we don't need to read from this pipe. */

    while (1)
    {
        in = fopen("input.txt", "r");
        while ((c = fgetc(in)) > 0) {
            ch = (char)c;
            /* write to child */
            rc = write(parent_to_child[1], &ch, 1);
            if (rc == -1) {
                perror("child: write");
                close(child_to_parent[0]);
                close(parent_to_child[1]);
                exit(1);
            }
            /* read back from child */
            rc = read(child_to_parent[0], &ch, 1);
            c = (int)ch;
            if (rc <= 0) {
                perror("parent: read");
                close(child_to_parent[0]);
                close(parent_to_child[1]);
                exit(1);
            }
            putchar(c);
        }
        fclose(in);
    }
    close(child_to_parent[0]);
    close(parent_to_child[1]);
    exit(0);
}

void do_child()
{
    int c;
    char ch;
    int rc;

    close(parent_to_child[1]); /* we don't need to write to this pipe.  */
    close(child_to_parent[0]); /* we don't need to read from this pipe. */

    //dup2(parent_to_child[0], STDIN_FILENO);
    //dup2(child_to_parent[1], STDOUT_FILENO);

    /* Some dup2() routines must be added here 
    to get this working as the first program above */

    char* cmd[] = { "bash", (char *)0 };
    execvp("bash", cmd);

    while (read(parent_to_child[0], &ch, 1) > 0) {
        c = (int)ch;
        ch = (char)c;
        putchar(ch);
        rc = write(child_to_parent[1], &ch, 1);
        if (rc == -1) {
            perror("child: write");
            close(parent_to_child[0]);
            close(child_to_parent[1]);
            exit(1);
        }
    }
    close(parent_to_child[0]);
    close(child_to_parent[1]);
    exit(0);
}

int main(int argc, char* argv[])
{
    int pid;
    int rc;

    umask(0);
    mknod("input.txt", S_IFIFO|0666, 0);    

    rc = pipe(parent_to_child);
    if (rc == -1) {
        perror("main: pipe parent_to_child");
        exit(1);
    }

    rc = pipe(child_to_parent);
    if (rc == -1) {
        perror("main: pipe child_to_parent");
        exit(1);
    }

    pid = fork();
    switch (pid) {
    case -1:
        perror("main: fork");
        exit(1);
    case 0:
        do_child();
    default:
        do_parent();
    }
    return 0;
}
A: 

The major difference is here:

    while ((c = fgetc(in)) > 0) {
        ch = (char)c;
        /* write to child */
        rc = write(parent_to_child[1], &ch, 1);
        /* .... */
        /* read back from child */
        rc = read(child_to_parent[0], &ch, 1);
        /* .... */
        putchar(c);
    }

As I'm lazy to compile/test for you, I would simply speculate that the parent is blocked in the read(). Because other side (bash in child process) isn't guaranteed to echo every written character back. Or it might even decide to print more than one character what your code is incapable of handling.

In the case you have to poll() to see whether there is something to read or not. Or set the O_NONBLOCK flag on the child_to_parent[0] with fcntl(F_SETFL) and when errno==EAGAIN, simply skip the read() branch. And loop while there are still characters to read.

Edit1. BTW I totally missed the part: you in do_parent() loop have to use poll() on the both child_to_parent[0] and in, since other side might write something (read() wouldn't block) even when you do not write() any character to it.

Dummy00001
A: 

Thanks to you it seems I got it's working.

So, here is updated code of do_parent:

void do_parent()
{
    int c;
    char ch;
    int rc;
    FILE *in;

    struct pollfd fds[2];
    int pol_ret;

    fds[0].fd = child_to_parent[0];

    close(child_to_parent[1]); /* we don't need to write to this pipe.  */
    close(parent_to_child[0]); /* we don't need to read from this pipe. */

    while (1)
    {   
        in = fopen("input.txt", "r");
        fds[1].fd = fileno(in);
        pol_ret = poll(fds, 2, 500);

        while ((c = fgetc(in)) > 0) {
            ch = (char)c;
            /* write to child */
            rc = write(parent_to_child[1], &ch, 1);
            if (rc == -1) {
                perror("child: write");
                close(child_to_parent[0]);
                close(parent_to_child[1]);
                exit(1);
            }
            /* read back from child */
            if (fds[0].revents & POLLIN)
            {
                rc = read(child_to_parent[0], &ch, 1);
                c = (int)ch;
                if (rc <= 0) {
                    perror("parent: read");
                    close(child_to_parent[0]);
                    close(parent_to_child[1]);
                    exit(1);
                }
                putchar(c);
            }
        }
        fclose(in);
    }
    close(child_to_parent[0]);
    close(parent_to_child[1]);
    exit(0);
}

Also I have added this into do_child():

dup2(parent_to_child[0], STDIN_FILENO);
0xDEFACE
Unfortunately, the problem is partly solved. This way is not working in Windows (compiling via Cygwin). If cygwin1.dll exists in the same dir as the program in, the result is the same as before. Within Cygwin shell everything is working fine.
0xDEFACE