Question

I am new in unix. In the following code, I pass three arguments from the command line "~$ foo last sort more" in order to replicate "~$ last | sort | more". I am trying to create a program that will take three argument(at least 3 for now). The parent will fork three processes. The first process will write to the pipe. The second process will read and write to and from the pipe and the third process will read from the pipe and write to the stdout(terminal). First process will exec "last", second process will exec "sort" and third process will exec "more" and the processes will sleep for 1,2 and 3 secs in order to synchronize. I am pretty sure I am having trouble creating a pipe and redirecting the input and output. I don't get any output to the terminal but I can see that the processes have been created. I would appreciate some help.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>

#define FOUND       1
#define NOT_FOUND   0
#define FIRST_CHILD 1
#define LAST_CHILD  numargc
#define PATH_1 "/usr/bin/"
#define PATH_2 "/bin/"

#define DUP_READ()                             \
if (dup2(fdes[READ], fileno(stdin)) == -1)     \
            {                              \
                perror("dup error");       \
                exit(4);                   \
            }                              

#define DUP_WRITE()                            \
if (dup2(fdes[WRITE], fileno(stdout)) == -1)   \
                {                              \
                    perror("dup error");       \
                    exit(4);                   \
                }                              

#define CLOSE_FDES_READ()   \
close(fdes[READ]);

#define CLOSE_FDES_WRITE()   \
close(fdes[WRITE]);

#define EXEC(x, y)                                          \
if (execl(arraycmds[x], argv[y], (char*)NULL) == -1)        \
                {                                           \
                    perror("EXEC ERROR");                   \
                    exit(5);                                \
                }
#define PRINT                         \
printf("FD IN:%d\n", fileno(stdin));    \
printf("FD OUT:%d\n", fileno(stdout));

enum 
{
    READ, /* 0 */
    WRITE,
    MAX
};

int cmdfinder( char* cmd, char* path); /* 1 -> found, 0 -> not found */
int main (int argc, char* argv[])
{

    int numargc=argc-1;
    char arraycmds[numargc][150];
    int i=1, m=0, sleeptimes=5, numfork;
    int rc=NOT_FOUND;
    pid_t pid;
    int fdes[2];

    if(pipe(fdes) == -1)
    {
        perror("PIPE ERROR");
        exit(4);
    }

    while(i <= numargc)
    {
        memset(arraycmds[m], 0, 150);
        rc=cmdfinder(argv[i], arraycmds[m]);
        if (rc)
        {
            printf("Command found:%s\n", arraycmds[m]);
        } 
        i++;
        m++;
    }

    i=0; //array index
    numfork=1; //fork number

    while(numfork <= numargc)
    {
        if ((pid=fork()) == -1)
        {
            perror("FORK ERROR");
            exit(3);
        }
        else if (pid == 0)
        {
            /* Child */

            sleep(sleeptimes);

            if (numfork == FIRST_CHILD)
            {
                DUP_WRITE();
                EXEC(i, numfork);
            }
            else if (numfork == LAST_CHILD)
            {

                DUP_READ();
                CLOSE_FDES_WRITE();
                EXEC(i, numfork);
            }
            else 
            {

                DUP_READ();
                DUP_WRITE();
                CLOSE_FDES_READ();
                CLOSE_FDES_WRITE();

                EXEC(i, numfork);
            }
        }
        else 
        {
            /* Parent */
            printf("pid:%d\n", pid);    
            i++;
            numfork++;
            sleeptimes++;
        }
    }

    PRINT;
    printf("i:%d\n", i);
    printf("numfork:%d\n", numfork);
    printf("DONE\n");       
    return 0;
}


int cmdfinder(char* cmd, char* path)
{
    DIR* dir;
    struct dirent *direntry; 
    char *pathdir;
    int searchtimes=2; 

    while (searchtimes)
    {
        pathdir = (char*)malloc(250);
        memset(pathdir, 0, 250);

        if (searchtimes==2)
        {
            pathdir=PATH_1;
        }
        else
        {
            pathdir=PATH_2;
        }

        if ((dir  = opendir(pathdir)) == NULL)
        {
            perror("Directory not found");
            exit (1);
        }
        else
        {
            while (direntry = readdir(dir))
            {
                if (strncmp( direntry->d_name, cmd, strlen(cmd)) == 0)
                {
                    strcat(path, pathdir);
                    strcat(path, cmd);
                    //searchtimes--;
                    return FOUND;
                }
            }
        }
        closedir(dir);
        searchtimes--;
    }
    printf("%s: Not Found\n", cmd);
    return NOT_FOUND;
}
Was it helpful?

Solution

All your macros are making this harder to read than if you just wrote it straight. Especially when they refer to local variables. To find out what's going on with EXEC my eyes have to jump up from where it's used to where it's defined, find out which local arrays it uses, then jump back down to see how that access fits in the flow of main. It's a maze of macros.

And wow, cmdfinder? Your very own $PATH lookup, only it's hardcoded /usr/bin:/bin? And double wow, readdir, just to find out if a file exists whose name is already decided? Just stat it! Or don't do anything, just exec it and handle the ENOENT by trying the next one. Or use execlp that's what it's there for!

On to the main point... you don't have enough pipes, and you're not closing all the unused descriptors.

last | sort | more is a pipeline of 3 commands connected by 2 pipes. You can't do it with one pipe. The first command should write into the first pipe, the middle command should read the first pipe and write to the second pipe, and the last command should read the second pipe.

You could create both pipes first, then do all the forks, which makes things simple to follow, but requires a lot of closes in every child process since they'll all inherit all the pipe fds. Or you can use a more sophisticated loop, creating each pipe just before forking the first process that will use it, and closing each descriptor in the parent as soon as the relevant child process has been created. I'd hate to see how many macros you'd use for that.

Every successful dup should be followed by a close of the descriptor that was copied. dup is short for "duplicate", not "move". After it's done, you have an extra descriptor left over, so don't just dup2(fdes[1], fileno(stdout) - also close(fdes[1]) afterward. (To be perfectly robust you should check whether fdes[1]==fileno(stdout) already, and in that case skip the dup2 and close.)

FOLLOWUP QUESTIONS

You can't use one pipe for 3 processes because there would be no way to distinguish which data should go to which destination. When the first process writes to the pipe, while both of the other processes are trying to read from it, one of them will get the data but you won't be able to predict which one. You need the middle process to read what the first process writes, and the last process to read what the middle process writes.

You're halfway right about file descriptors being shared after a fork. The actual pipe object is shared. That's what makes the whole system work. But the file descriptors - the endpoints designated by small integers like 1 for standard output, 0 for standard input, and so on - are not coupled the way you suggest. The same pipe object may be associated with the same file descriptor number in two processes, the associations are independent. Closing fd 1 in one process does not cause fd 1 to become closed in any other process, even if they are related.

Sharing of the fd table, so that a close in one task has an effect in another task, is part of the "pthread" feature set, not the "fork" feature set.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top