Andrew Raleigh
Andrew Raleigh

Reputation: 31

Implementing piping to mimic Shell

reader. I'm writing a C program that mimics the linux shell. Before implementing piping, my code worked normally, and on output/input files. Next was implementing command piping such as (a|b)|c. Below is my code:

Testing it, I get the proper returns for "sfhjdj", "exit", and "cd". The issue that I have is that running a simple command returns ls: write error: Bad file descriptor. Attempting to pipe only runs the first function as well. Any idea on what's causing this? I attempted to pipe as I've seen from other questions here.

Below is my code before implementing piping, I can't find the error but it might be because of closing/duping. Thank you for reading this!

Only change was in the execute function.

EDIT: Helper code, prase.c

/*
 * parse.c - feeble command parsing for the Feeble SHell.
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "parse.h"
#include "error.h"


#define MAXARGV 1000

enum token {
    identifier, directin, directout, doubledirectout,
    /* everything >= semicolon ends an individual "struct pipeline" */
    semicolon,
    ampersand,
    verticalbar, doubleampersand, doublebar, doublepipe,
    eol
};
static enum token gettoken(char **s, char **argp);
static char *ptok(enum token tok);


struct parsed_line *parse(char *s)
{
    struct parsed_line *retval;  /* remains freeparse()able at all times */
    struct parsed_line *curline;
    struct pipeline **plp;  /* where to append for '|' and '|||' */
    char *argv[MAXARGV];
    enum token tok;
    int argc = 0;
    int isdouble = 0;

    retval = curline = emalloc(sizeof(struct parsed_line));
    curline->conntype = CONN_SEQ;  /* i.e. always do this first command */
    curline->inputfile = curline->outputfile = NULL;
    curline->output_is_double = 0;
    curline->isbg = 0;
    curline->pl = NULL;
    curline->next = NULL;
    plp = &(curline->pl);

    do {
        if (argc >= MAXARGV)
            fatal("argv limit exceeded");
        while ((tok = gettoken(&s, &argv[argc])) < semicolon) {
            switch ((int)tok) {  /* cast prevents stupid warning message about
                                  * not handling all enum token values */
            case identifier:
                argc++;  /* it's already in argv[argc];
                          * increment to represent a save */
                break;
            case directin:
                if (curline->inputfile) {
                    fprintf(stderr,
                            "syntax error: multiple input redirections\n");
                    freeparse(curline);
                    return(NULL);
                }
                if (gettoken(&s, &curline->inputfile) != identifier) {
                    fprintf(stderr, "syntax error in input redirection\n");
                    freeparse(curline);
                    return(NULL);
                }
                break;
            case doubledirectout:
                curline->output_is_double = 1;
                /* fall through */
            case directout:
                if (curline->outputfile) {
                    fprintf(stderr,
                            "syntax error: multiple output redirections\n");
                    freeparse(curline);
                    return(NULL);
                }
                if (gettoken(&s, &curline->outputfile) != identifier) {
                    fprintf(stderr, "syntax error in output redirection\n");
                    freeparse(curline);
                    return(NULL);
                }
                break;
            }
        }

        /* cons up just-parsed pipeline component */
        if (argc) {
            *plp = emalloc(sizeof(struct pipeline));
            (*plp)->next = NULL;
            (*plp)->argv = eargvsave(argv, argc);
            (*plp)->isdouble = isdouble;
            plp = &((*plp)->next);
            isdouble = 0;
            argc = 0;
        } else if (tok != eol) {
            fprintf(stderr, "syntax error: null command before `%s'\n",
                    ptok(tok));
            freeparse(curline);
            return(NULL);
        }

        /* ampersanded? */
        if (tok == ampersand)
            curline->isbg = 1;

        /* is this a funny kind of pipe (to the right)? */
        if (tok == doublepipe)
            isdouble = 1;

        /* does this start a new struct parsed_line? */
        if (tok == semicolon || tok == ampersand || tok == doubleampersand || tok == doublebar) {
            curline->next = emalloc(sizeof(struct parsed_line));
            curline = curline->next;

            curline->conntype =
                (tok == semicolon || tok == ampersand) ? CONN_SEQ
                : (tok == doubleampersand) ? CONN_AND
                : CONN_OR;
            curline->inputfile = curline->outputfile = NULL;
            curline->output_is_double = 0;
            curline->isbg = 0;
            curline->pl = NULL;
            curline->next = NULL;
            plp = &(curline->pl);
        }

    } while (tok != eol);
    return(retval);
}


/* (*s) is advanced as we scan; *argp is set iff retval == identifier */
static enum token gettoken(char **s, char **argp)
{
    char *p;

    while (**s && isascii(**s) && isspace(**s))
        (*s)++;
    switch (**s) {
    case '\0':
        return(eol);
    case '<':
        (*s)++;
        return(directin);
    case '>':
        (*s)++;
        if (**s == '&') {
            (*s)++;
            return(doubledirectout);
        }
        return(directout);
    case ';':
        (*s)++;
        return(semicolon);
    case '|':
        if ((*s)[1] == '|') {
            *s += 2;
            return(doublebar);
        }
        (*s)++;
        if (**s == '&') {
            (*s)++;
            return(doublepipe);
        }
        return(verticalbar);
    case '&':
        if ((*s)[1] == '&') {
            *s += 2;
            return(doubleampersand);
        } else {
            (*s)++;
            return(ampersand);
        }
    /* else identifier */
    }

    /* it's an identifier */
    /* find the beginning and end of the identifier */
    p = *s;
    while (**s && isascii(**s) && !isspace(**s) && !strchr("<>;&|", **s))
        (*s)++;
    *argp = estrsavelen(p, *s - p);
    return(identifier);
}


static char *ptok(enum token tok)
{
    switch (tok) {
    case directin:
        return("<");
    case directout:
        return(">");
    case semicolon:
        return(";");
    case verticalbar:
        return("|");
    case ampersand:
        return("&");
    case doubleampersand:
        return("&&");
    case doublebar:
        return("||");
    case doubledirectout:
        return(">&");
    case doublepipe:
        return("|&");
    case eol:
        return("end of line");
    default:
        return(NULL);
    }
}


static void freepipeline(struct pipeline *pl)
{
    if (pl) {
        char **p;
        for (p = pl->argv; *p; p++)
            free(*p);
        free(pl->argv);
        freepipeline(pl->next);
        free(pl);
    }
}


void freeparse(struct parsed_line *p)
{
    if (p) {
        freeparse(p->next);
        if (p->inputfile)
            free(p->inputfile);
        if (p->outputfile)
            free(p->outputfile);
        freepipeline(p->pl);
        free(p);
    }

Builtin.c - for cd and exit

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "fsh.h"
#include "builtin.h"


int builtin_exit(char **argv)
{
    if (argv[1] && argv[2]) /* i.e. argc >= 2 */ {
        fprintf(stderr, "usage: exit [status]\n");
        fflush(stderr);
        return(1);
    } else if (argv[1]) {
        /* "exit ###" */
        exit(atoi(argv[1]));
    } else {
        /* "exit" with no argument */
        exit(laststatus);
    }
}

int builtin_cd(char **argv)
{
    if (argv[1] && argv[2]) {
        fprintf(stderr, "usage: %s dir\n", argv[0]);
        return(1);
    } else if (argv[1]) {
        chdir(argv[1]);
    } else {
        chdir(getenv("HOME"));
    }
    //if (chdir(argv[1])) {
    //    perror(argv[1]);
    //    return(1);
    //}
    return(0);
}

Parse.h - how the pipeline is

enum connenum {
    CONN_SEQ,  /* sequential commands, i.e. separated by a semicolon */
    CONN_AND,  /* commands joined by '&&' */
    CONN_OR    /* commands joined by '||' */
};

struct pipeline {  /* list of '|'-connected commands */
    char **argv;  /* array ending with NULL */
    struct pipeline *next;  /* NULL if this doesn't pipe into anything */
    int isdouble; /* 1 if we have '|&' i.e. should dup onto 2 */
};

struct parsed_line { /* list of ';' or '&&' or '||' -connected struct pipelines */
    enum connenum conntype;  /* will be CONN_SEQ if this is the first item */
    char *inputfile, *outputfile;  /* NULL for no redirection */
    int output_is_double;  /* output redirection is '>&' rather than '>' */
    struct pipeline *pl;  /* the command(s) */
    int isbg;  /* non-zero iff there is a '&' after this command */
    struct parsed_line *next;   /* connected as specified by next->conntype */
};


extern struct parsed_line *parse(char *s);
extern void freeparse(struct parsed_line *p);

Upvotes: 1

Views: 1670

Answers (1)

user1123335
user1123335

Reputation:

Ref Creating Pipes in C

Ref GNU Pipe to a Subprocess

May help you if you really want to get down to the coal face.

This may help you if you prefer to use popen

Ref Pipes the Easy Way using popen

I've test this and it does what you want Eg opens 2 pipes (The ls and sort command) Notice the r attribute in popen("ls", "r") which i suspect may have been the issue with the above code.

Eg

If the parent wants to receive data from the child, it should close fd1, and the child should close fd0. If the parent wants to send data to the child, it should close fd0, and the child should close fd1.

Since descriptors are shared between the parent and child, we should always be sure to close the end of pipe we aren't concerned with. On a technical note, the EOF will never be returned if the unnecessary ends of the pipe are not explicitly closed.

The file descriptor for the pipe may not have been set to Read / Write or the File descriptors closed in the right order..

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE *pipein_fp, *pipeout_fp;
    char readbuf[80];

    /* Create one way pipe line with call to popen() */
    if (( pipein_fp = popen("ls", "r")) == NULL)
    {
            perror("popen");
            exit(1);
    }

    /* Create one way pipe line with call to popen() */
    if (( pipeout_fp = popen("sort", "w")) == NULL)
    {
            perror("popen");
            exit(1);
    }

    /* Processing loop */
    while(fgets(readbuf, 80, pipein_fp))
            fputs(readbuf, pipeout_fp);

    /* Close the pipes */
    pclose(pipein_fp);
    pclose(pipeout_fp);

    return(0);
}

If you need to Parsing Program Arguments

Getopt: Parsing program options using getopt.

Argp: Parsing program options using argp_parse.

Suboptions: Some programs need more detailed options.

May help you.

All the best

Upvotes: 1

Related Questions