fd leak, custom Shell

后端 未结 2 1404
臣服心动
臣服心动 2020-12-21 11:06

I\'m working on a custom shell that can handle multiple pipes. But every time I execute a new pipeline and check the process with ls -l /proc/pid/fd I get somet

2条回答
  •  情话喂你
    2020-12-21 11:42

    As tadman pointed out, it is easier to use a command struct to pass things around.

    We can't [well, we could but shouldn't] do a wait [in the parent] during pipe construction. That has to be a separate loop later. We would hang the parent after the first child is created.

    If the first child had a large amount of output, the kernel pipe buffers might fill up and the first child would block. But, since the second child has not been created, there is nothing to read/drain the first child's output and unblock it.

    Also, it is important to close the pipe units after doing dup2 and ensure the previous pipe stage units are closed in the parent.

    Here's a refactored version that does all that.

    As to your original issue with file descriptor leakage, I think I fixed that by adding some more close calls. The program has some self verification code on this:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define FREEME(ptr_) \
        do { \
            if (ptr_ == NULL) \
                break; \
            free(ptr_); \
            ptr_ = NULL; \
        } while (0)
    
    #define CLOSEME(fd_) \
        do { \
            if (fd_ < 0) \
                break; \
            close(fd_); \
            fd_ = -1; \
        } while (0)
    
    // command control
    typedef struct {
        unsigned int cmd_opt;               // options
        int cmd_cldno;                      // child number
    
        char *cmd_buf;                      // command buffer
        int cmd_argc;                       // argument count
        char **cmd_argv;                    // arguments
    
        int cmd_pipe[2];                    // pipe units
        pid_t cmd_pid;                      // child pid number
        int cmd_status;                     // child status
    } cmd_t;
    
    #define CMD_FIRST       (1u << 0)
    #define CMD_LAST        (1u << 1)
    
    char linebuf[1000];
    int cmdcount;
    cmd_t *cmdlist;
    
    int opt_d;
    int opt_l;
    
    #define dbg(fmt_...) \
        do { \
            if (opt_d) \
                printf(fmt_); \
        } while (0)
    
    // show open fd's
    void
    fdshow1(int cldid)
    {
        char buf[100];
    
        fprintf(stderr,"CLD: %d\n",cldid);
        sprintf(buf,"ls -l /proc/%d/fd 1>&2",getpid());
        system(buf);
    }
    
    // show open fd's
    void
    fdshow2(int cldid)
    {
        char dir[100];
        char lnkfm[1000];
        char lnkto[1000];
        int len;
        DIR *xf;
        struct dirent *ent;
        char *bp;
        char obuf[1000];
    
        sprintf(dir,"/proc/%d/fd",getpid());
        xf = opendir(dir);
    
        bp = obuf;
        bp += sprintf(bp,"%d:",cldid);
    
        while (1) {
            ent = readdir(xf);
            if (ent == NULL)
                break;
    
            if (strcmp(ent->d_name,".") == 0)
                continue;
            if (strcmp(ent->d_name,"..") == 0)
                continue;
    
            sprintf(lnkfm,"%s/%s",dir,ent->d_name);
            len = readlink(lnkfm,lnkto,sizeof(lnkto));
            lnkto[len] = 0;
    
            if (strstr(lnkto,"pipe") != 0)
                bp += sprintf(bp," %s-->%s",ent->d_name,lnkto);
    
            switch (ent->d_type) {
            case DT_FIFO:
                break;
            }
        }
    
        bp += sprintf(bp,"\n");
        fputs(obuf,stderr);
        fflush(stderr);
    
        closedir(xf);
    }
    
    // show open fd's
    void
    fdshow(int cldid)
    {
    
        fdshow2(cldid);
    }
    
    // pipeadd -- add single command to pipe
    void
    pipeadd(char *buf)
    {
        char *cp;
        char *bp;
        char *sv;
        cmd_t *cmd;
    
        dbg("pipeadd: buf='%s'\n",buf);
    
        cmdlist = realloc(cmdlist,(cmdcount + 1) * sizeof(cmd_t));
    
        cmd = &cmdlist[cmdcount];
        memset(cmd,0,sizeof(cmd_t));
        cmd->cmd_pipe[0] = -1;
        cmd->cmd_pipe[1] = -1;
    
        cmd->cmd_cldno = cmdcount;
        ++cmdcount;
    
        bp = buf;
        while (1) {
            cp = strtok_r(bp," \t",&sv);
            bp = NULL;
            if (cp == NULL)
                break;
    
            cmd->cmd_argv = realloc(cmd->cmd_argv,
                (cmd->cmd_argc + 2) * sizeof(char **));
    
            cmd->cmd_argv[cmd->cmd_argc + 0] = cp;
            cmd->cmd_argv[cmd->cmd_argc + 1] = NULL;
    
            cmd->cmd_argc += 1;
        }
    }
    
    // pipesplit -- read in and split up command
    void
    pipesplit(void)
    {
        char *cp;
        char *bp;
        char *sv;
        cmd_t *cmd;
    
        printf("> ");
        fflush(stdout);
    
        fgets(linebuf,sizeof(linebuf),stdin);
    
        cp = strchr(linebuf,'\n');
        if (cp != NULL)
            *cp = 0;
    
        bp = linebuf;
        while (1) {
            cp = strtok_r(bp,"|",&sv);
            bp = NULL;
    
            if (cp == NULL)
                break;
    
            pipeadd(cp);
        }
    
        cmd = &cmdlist[0];
        cmd->cmd_opt |= CMD_FIRST;
    
        cmd = &cmdlist[cmdcount - 1];
        cmd->cmd_opt |= CMD_LAST;
    
        if (opt_d) {
            for (cmd_t *cmd = cmdlist;  cmd < &cmdlist[cmdcount];  ++cmd) {
                dbg("%d:",cmd->cmd_cldno);
                for (int argc = 0;  argc < cmd->cmd_argc;  ++argc)
                    dbg(" '%s'",cmd->cmd_argv[argc]);
                dbg("\n");
            }
        }
    }
    
    // pipefork -- fork elements of pipe
    void
    pipefork(void)
    {
        cmd_t *cmd;
        int fdprev = -1;
        int fdpipe[2] = { -1, -1 };
    
        for (cmd = cmdlist;  cmd < &cmdlist[cmdcount];  ++cmd) {
            // both parent and child should close output side of previous pipe
            CLOSEME(fdpipe[1]);
    
            // create a new pipe for the output of the current child
            if (cmd->cmd_opt & CMD_LAST) {
                fdpipe[0] = -1;
                fdpipe[1] = -1;
            }
            else
                pipe(fdpipe);
    
            cmd->cmd_pid = fork();
            if (cmd->cmd_pid < 0) {
                printf("pipefork: fork fail -- %s\n",strerror(errno));
                exit(1);
            }
    
            // parent the input side for the next pipe stage
            if (cmd->cmd_pid != 0) {
                CLOSEME(fdprev);
                fdprev = fdpipe[0];
                continue;
            }
    
            // connect up our input to previous pipe stage's output
            if (fdprev >= 0) {
                dup2(fdprev,0);
                CLOSEME(fdprev);
            }
    
            // connect output side of our pipe to stdout
            if (fdpipe[1] >= 0) {
                dup2(fdpipe[1],1);
                CLOSEME(fdpipe[1]);
            }
    
            // child doesn't care about reading its own output
            CLOSEME(fdpipe[0]);
    
            if (opt_l)
                fdshow(cmd->cmd_cldno);
    
            // off we go ...
            execvp(cmd->cmd_argv[0],cmd->cmd_argv);
        }
    
        CLOSEME(fdpipe[0]);
        CLOSEME(fdpipe[1]);
    
        if (opt_l)
            fdshow(-1);
    }
    
    // pipewait -- wait for pipe stages to complete
    void
    pipewait(void)
    {
        pid_t pid;
        int status;
        int donecnt = 0;
    
        while (donecnt < cmdcount) {
            pid = waitpid(0,&status,0);
            if (pid < 0)
                break;
    
            for (cmd_t *cmd = cmdlist;  cmd < &cmdlist[cmdcount];  ++cmd) {
                if (pid == cmd->cmd_pid) {
                    cmd->cmd_status = status;
                    ++donecnt;
                    break;
                }
            }
        }
    }
    
    // pipeclean -- free all storage
    void
    pipeclean(void)
    {
    
        for (cmd_t *cmd = cmdlist;  cmd < &cmdlist[cmdcount];  ++cmd)
            FREEME(cmd->cmd_argv);
        FREEME(cmdlist);
        cmdcount = 0;
    }
    
    // main -- main program
    int
    main(int argc,char **argv)
    {
        char *cp;
    
        --argc;
        ++argv;
    
        for (;  argc > 0;  --argc, ++argv) {
            cp = *argv;
            if (*cp != '-')
                break;
    
            switch (cp[1]) {
            case 'd':
                opt_d = ! opt_d;
                break;
    
            case 'l':
                opt_l = ! opt_l;
                break;
    
            default:
                break;
            }
        }
    
        while (1) {
            pipesplit();
            pipefork();
            pipewait();
            pipeclean();
        }
    
        return 0;
    }
    

提交回复
热议问题