Reading a Unix Shell Command into a String/Linked List using pipes and fork()

179 views Asked by At

I'm trying to write a program using C99/gcc/Linux(unistd.h) to take in Linux shell commands from the user, execute them, retrieve their output and store it in a dynamically sized array/string for outputting/modifying at a later date.

In my code below, I'm passing the shell commands through to this function one at a time (char * cmd) and I'm hoping to retrieve their output either using a linked list of characters (node * charLinkedList) or even a dynamically allocated string large enough to fit all the data, as some commands could return 5 characters (pwd) while others could return a lot (cat [file]).

I have thoroughly tested and confirmed the following is working:

  • storing / retrieving / printing to the character linked list
  • forking the process and having the child process execute the commands (not using pipes)
  • executing the shell command and printing it to the console (not using pipes)
  • executing shell commands with and without arguments of varying lengths

What is not working:

  • piping the output from each command to a variable / linked list
#define READPIPE 0  
#define WRITEPIPE 1
/* % Inputs %
 * char * cmd : a string containing the command
 * node * charLinkedList : a linked list of char to store the output */
void execCmd(char * cmd, node * charLinkedList) {
    int fd[2];        // fd is the pipe
    pid_t pid;        // pid is for holding the process ID

    char* shellOutput;  // shellOutput is a string to store the output in, it's
                        //> not being used in this code but I'm ok using it in place
                        //> of my character linked list (charLinkedList)

    if(pipe(fd) == -1){
        fprintf(stderr,"Pipe failed");
    }
    
    /* Logic for separating the command from it's arguments */
    int argNum = argumentCounter(cmd);   // argNum counts the arguments and returns it
    char * args[argNum + 2];            // for holding the arguments that we will be passing later on
    char ** delimArguments;             // to hold the deliminated command string

    if(argNum > 0) {                                //if there are arguments with the command
        delimArguments = stringSplitter(cmd, ' ');  //stringSplitter splits the string at a deliminer and returns it
        for (int i = 0; i < (argNum + 1); i++){     //loads the commands into an array
            args[i] = *(delimArguments + i);        // this logic has been tested and works
        }
        args[argNum + 1] = 0;                       // please ignore this spaghetti code, I'll clean
    }                                               //> it up later I promise
    
    /* Forking & executing the commands */
    pid = fork();
    if(pid == -1) {
        printf("FORK FAILED\n");
    }

    if (pid == 0) { /* Child Process */
        
        close(fd[READPIPE]); // same as close(fd[0])
        
        /* This next if/else statement is for toggling between whether or not the
         * command has arguments or not. It's a mess and I'll 100% be merging 
         * this at a later date. */

        if (argNum > 0){
            /* command with arguments */ //i.e. ls -l -a
            dup2(fd[WRITEPIPE], STDOUT_FILENO);  //duplicate the STDOUT to the write end of the pipe (fd[1])
            execvp(args[0], args);               //execute the command and it's arguments
        } else {
            /* command without argument */ //i.e. ls
            dup2(fd[WRITEPIPE], STDOUT_FILENO); //duplicate the STDOUT to the write end of the pipe (fd[1])
            execlp(cmd, cmd, NULL);             //execute the command
        }
        close(fd[WRITEPIPE]);  //if something goes wrong and the code gets to here, close the pipe
        return;      //return to int main() function; don't continue this one as the child
    } else { /* Parent Process */
        close(fd[WRITEPIPE]);       //closing the writing side of the pipe
        fcntl(fd[READPIPE], F_SETFL, fcntl(fd[READPIPE], F_GETFL) | O_NONBLOCK); //using fcntl to get the file descriptor of the pipe

        FILE * theOutput;                          // creating a file to point at the output
        theOutput = fdopen(fd[READPIPE], "r");     // opening the stream from the child process
        char buffer;                               // a char for grabbing output one character at a time
        while((buffer = fgetc(theOutput)) != NULL) // while we havn't hit the end of the file
        {
            addToQueue(charLinkedList, buffer);    // one by one add each character of the output to
        }                                          //> the linked list queue
        printLinkedList(charLinkedList);           //outputting the list to verify the output captured
    }

I tried using read() and write() functions, although I couldn't get that to work. I implemented dup2 to clone STDOUT to the pipe and then fought with it from there, trying to get it to work (with no success). I found another post suggesting doing things the way I have them currently, although I can't figure out how to get output from the file descriptor.

I've tried using both fgetc and fgets to no avail and I'm kinda stuck at this point. Is this the right way to do it or is there a better way for getting execvp output from a child process into a string / linked list / variable in the parent process?

0

There are 0 answers