2
votes

I am a bit of a newbie in programming Linux in C (I searched for similar threads but none helped), so I got stuck at the following problem :

I want to create a shell in C for Linux (using fork(), exec(), pipe(), that gets a command with parameters and pipes as input from the terminal stdin (ex. "sort foo | uniq -c | wc -l"), it executes it and then asks for the next command etc.

I separated the different commands, their parameters etc, I created 1 child process for each one, but I can't chain the output of each child process to the input of the next one (and the last output at stdout in terminal).

Could anyone help do the correct piping to get it up and running ??

For any more info, just ask... Thanks in advance

The full code is below:

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

#define P_READ 0
#define P_WRITE 1

pid_t pID, chID;
char input[100];
char * params[100][100];
char * funcs[100];
char * par;
char * fun;
int i, j, k, stat, infd[2], outfd[2];


//Pipe read
void read_en(int * infd)
{
    dup2(infd[P_READ], STDIN_FILENO);
    close(infd[P_READ]);
    close(infd[P_WRITE]);
}

//Pipe write
void write_en(int * outfd)
{
    dup2(outfd[P_WRITE], STDOUT_FILENO);
    close(outfd[P_READ]);
    close(outfd[P_WRITE]);
}

//Fork, read from pipe, write to pipe, exec   
void fork_chain(int * infd, int * outfd, int i)
{
        pID = fork();

        if (pID == 0)
        {
            if (infd != NULL)
            {
                read_en(infd);
            }

            if (outfd != NULL)
            {
                write_en(outfd);
            }

            execvp(params[i][0], params[i]);
            fprintf(stderr, "Command not found!\n");
            exit(1);
        }
        else if (pID < 0)
        {
            fprintf(stderr, "Fork error!\n");
            exit(1);
        }
        else
        {
            chID = waitpid(-1, &stat, 0);
        }
}


int main()
{
    printf("\n$");
    fgets(input, sizeof(input), stdin);
    strtok(input, "\n");


    while (strcmp(input, "exit") != 0)
    {
        //Separate each command
        k = 0;
        fun = strtok(input, "|");

        while (fun != NULL)
        {
            funcs[k] = fun;
            fun = strtok(NULL, "|");
            k++;
        }


        //Separate each command's parameters
        for (i = 0; i < k; i++)
        {
            j = 0;
            par = strtok(funcs[i], " ");

            while (par != NULL)
            {
                params[i][j] = par;
                par = strtok(NULL, " ");
                j++;
            }
            params[i][j] = NULL;
        }

        //Fork, pipe and exec for each command
        for (i = 0; i < k; i++)
        {
            if (i == 0)
            {
                pipe(outfd);
                fork_chain(NULL, outfd, 0);
                infd[P_READ] = outfd[P_READ];
                infd[P_WRITE] = outfd[P_WRITE];
            }
            else if (i == k-1)
            {
                fork_chain(infd, NULL, 1);
                close(infd[P_READ]);
                close(infd[P_WRITE]);
            }
            else
            {
                pipe(outfd);
                fork_chain(infd, outfd, i);
                close(infd[P_READ]);
                close(infd[P_WRITE]);
                infd[P_READ] = outfd[P_READ];
                infd[P_WRITE] = outfd[P_WRITE];
            }
        }

        //Ask for next input
        printf("\n$");
        fgets(input, sizeof(input), stdin);
        strtok(input, "\n");
    }

    return (0);
}
1
Related if not a duplicate: stackoverflow.com/q/5060350/694576alk
The thread you referred to, is about 2 childs only. I want more than 2, that's the problem...Ezlen

1 Answers

0
votes

The shell process must close the write end of the pipe, because otherwise the child process does not get EOF on its input and blocks forever when calling read(). Your code would close the write end, but too late in case of the last child, because fork_chain() waits for it to exit (which bears another problem if the pipe buffer gets full) before returning. If we close the write end at the right time, the close(infd[P_WRITE]) has to be removed from read_en(), since it will already be closed by then.
Aside, there is an error due to the code multiplication at

            else if (i == k-1)
            {
                fork_chain(infd, NULL, 1);

the argument 1 is only correct if k equals 2, in the general case it must be i.

So, along with the change in read_en(), the main fork-pipe-and-exec loop could be rewritten to

        for (i = 0; i < k; i++)
        {
            fork_chain(i ? infd : NULL, i < k-1 ? pipe(outfd), outfd : NULL, i);
            if (i)
                close(infd[P_READ]);
            if (i < k-1)
                infd[P_READ] = outfd[P_READ],
                close(outfd[P_WRITE]);  // shell is done with it
        }

(note that infd[P_WRITE] is no longer used anywhere).