Search code examples
cpipeforkdup2

How to use pipe and dup2 in c


I have to simulate this command using pipes in c: echo "<exp>" | bc -lq. Process A must read a string and send it to process B; Process B executes the "bc -lq" command and returns the result to A.

The code is this, but I can't understand why it doesn't work; in particular, the "bc" command appears to be unable to read the expression from stdin.

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>

#define N 1024

#define LEGGI(stringa)  if(scanf("%s", stringa) == 0)                 \
                        {                                             \
                            perror("Impossibile leggere la stringa"); \
                            exit(EXIT_FAILURE);                       \
                        }                                             \

void closePipe(int pipeFd);
void Dup2(int pipeTempFd, int fd);
void Write(int pipeTempFd, char stringa[], int n);
void Read(int pipeTempFd, char stringa[], int n);

int main()
{

    char stringa[N];
    LEGGI(stringa);

    int pipeFd[2];
    int pRicezioneFd[2];

    if(pipe(pipeFd) == -1 || pipe(pRicezioneFd) == -1)
    {
        perror("impossibile eseguire la pipe");
        exit(EXIT_FAILURE);
    }

    if(strncmp(stringa, "exit", N) != 0)
    {
        pid_t processPid;
        if((processPid = fork()) == 1)
        {
            perror("impossibile eseguire la fork");
            exit(EXIT_FAILURE);
        }

        if(processPid != 0)
        {
            closePipe(pipeFd[0]);
            closePipe(pRicezioneFd[1]);

            printf("operazione: %s\n", stringa);

            Write(pipeFd[1], stringa, N);
            closePipe(pipeFd[1]);

            read(pRicezioneFd[0], stringa, N);

            closePipe(pRicezioneFd[0]);

            if(wait(NULL) == -1)
            {
                perror("Impossibile eseguire la wait");
                exit(EXIT_FAILURE);
            }

            printf("%s\n", stringa);
        }
        else
        {
            closePipe(pipeFd[1]);
            closePipe(pRicezioneFd[0]);

            Dup2(pipeFd[0], 0);
            Dup2(pRicezioneFd[1], 1);
            Dup2(pRicezioneFd[1], 2);


            // closePipe(pipeFd[0]);
            // closePipe(pRicezioneFd[1]);

            if(execl("/usr/bin/bc", "bc", "-lq", NULL) == -1)
            {
                perror("programma non reperibile");
                exit(EXIT_FAILURE);
            }
        }
    }

    return 0;
}
void closePipe(int pipeFd)
{
    if(close(pipeFd) == -1)
    {
        perror("impossibile chiudere il fd della pipe");
        exit(EXIT_FAILURE);
    }
}

void Dup2(int pipeTempFd, int fd)
{
    if(dup2(pipeTempFd, fd) == -1)
    {
        perror("Impossibile eseguire la dup2");
         exit(EXIT_FAILURE);
    }
}

void Write(int pipeTempFd, char stringa[], int n)
{
    if(write(pipeTempFd, stringa, n) == -1)
    {
        perror("impossibile scrivere sulla pipa");
        exit(EXIT_FAILURE);
    }
}

void Read(int pipeTempFd, char stringa[], int n)
{
    if(read(pipeTempFd, stringa, n) == -1)
    {
        perror("impossibile leggere dalla pipe pipa");
        exit(EXIT_FAILURE);
    }
}

Solution

  • I think my code is similar to yours, one main change was the arguments to the exec call. Another change was that I only have 2 dup2 calls in the child process, one for changing stdin and the other for stdout. You only need to change these.

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<sys/wait.h>
    
    #define N 1024
    #define READ_END 0
    #define WRITE_END 1
    
    int main()
    {
        char *input = NULL;
        size_t len = 0;
        ssize_t amt_read = 0;
    
        puts("Please enter a string: ");
        amt_read = getline(&input, &len, stdin);
    
        if(amt_read < 0)
            exit(EXIT_FAILURE);
    
        pid_t pid;
    
        int fd[2];
        int fd_return[2];
    
        // Create the pipes
        pipe(fd);
        pipe(fd_return);
    
        pid = fork();
    
        if(pid==0)
        {  // If child process
            dup2(fd[READ_END], STDIN_FILENO);
            dup2(fd_return[WRITE_END], STDOUT_FILENO);
    
            close(fd[WRITE_END]);
            close(fd[READ_END]);
    
            close(fd_return[WRITE_END]);
            close(fd_return[READ_END]);
    
            execl("/usr/bin/bc", "/usr/bin/bc", "-lq", (char *)NULL);
    
            fprintf(stderr, "Failed to execute\n");
            exit(EXIT_FAILURE);
        }
        else
        { // If parent process
            int status;
            close(fd[READ_END]);
            close(fd_return[WRITE_END]);
    
            // Write to the pipe
            write(fd[WRITE_END], input, strlen(input));
    
            close(fd[WRITE_END]);
    
            // Wait for the child to finish
            waitpid(pid, &status, 0);
    
            char buff[N];
            buff[N-1] = 0;
    
            read(fd_return[READ_END], buff, N-1);
            *(index(buff, '\n')) = '\0'; // Add null terminator on your own
    
            close(fd_return[READ_END]);
    
            printf("The Result is: %s\n", buff);
    
        }
    
        free(input);
    
        return 0;
    }
    

    EDIT: I edited the code and debugged it. I also changed the user input schematic so now you no longer allocated static storage for the user input, rather it is allocated dynamically and then this storage is freed at the end with free();

    Note, I did leave the static storage for reading the input back in, you can change this in the future if you want.

    illegal character: ^@ comes from the fact that write() was writing too much, notice that now we only write strlen(buff) amount.

    Note: The input to bc will not work unless the string ends like "...\n\0" luckily getline() does this by default.

    Also, I left all the pipe closing the same, that was not causing the issue.